<!DOCTYPE html>
<html lang="en">





<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/img/favicon.jpg">
  <link rel="icon" type="image/png" href="/img/favicon.jpg">
  <meta name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="description" content="">
  <meta name="author" content="John Doe">
  <meta name="keywords" content="">
  <title>Netty - Nyima&#39;s Blog</title>

  <link  rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />


  <link  rel="stylesheet" href="https://cdn.staticfile.org/github-markdown-css/4.0.0/github-markdown.min.css" />
  <link  rel="stylesheet" href="/lib/hint/hint.min.css" />

  
    <link  rel="stylesheet" href="https://cdn.staticfile.org/highlight.js/10.0.0/styles/github-gist.min.css" />
  

  


<!-- 主题依赖的图标库，不要自行修改 -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_yg9cfy8wd6.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_pjno9b9zyxs.css">


<link  rel="stylesheet" href="/css/main.css" />

<!-- 自定义样式保持在最底部 -->


  <script  src="/js/utils.js" ></script>
<meta name="generator" content="Hexo 4.2.1"></head>


<body>
  <header style="height: 70vh;">
    <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand"
       href="/">&nbsp;<strong>Nyima</strong>&nbsp;</a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/">
                <i class="iconfont icon-home-fill"></i>
                Home
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/archives/">
                <i class="iconfont icon-archive-fill"></i>
                Archives
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/categories/">
                <i class="iconfont icon-category-fill"></i>
                Categories
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/tags/">
                <i class="iconfont icon-tags-fill"></i>
                Tags
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/links/">
                <i class="iconfont icon-link-fill"></i>
                Links
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/about/">
                <i class="iconfont icon-user-fill"></i>
                About
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" data-toggle="modal" data-target="#modalSearch">&nbsp;&nbsp;<i
                class="iconfont icon-search"></i>&nbsp;&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

    <div class="view intro-2" id="background" parallax=true
         style="background: url('/img/3.jpg') no-repeat center center;
           background-size: cover;">
      <div class="full-bg-img">
        <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
          <div class="container text-center white-text fadeInUp">
            <span class="h2" id="subtitle">
              
            </span>

            
              
  <div class="mt-3 post-meta">
    <i class="iconfont icon-date-fill" aria-hidden="true"></i>
    <time datetime="2021-04-25 10:24">
      April 25, 2021 am
    </time>
  </div>


<div class="mt-1">
  
    
    <span class="post-meta mr-2">
      <i class="iconfont icon-chart"></i>
      30.2k 字
    </span>
  

  
    
    <span class="post-meta mr-2">
      <i class="iconfont icon-clock-fill"></i>
      
      
      442
       分钟
    </span>
  

  
  
</div>

            
          </div>

          
        </div>
      </div>
    </div>
  </header>

  <main>
    
      

<div class="container-fluid">
  <div class="row">
    <div class="d-none d-lg-block col-lg-2"></div>
    <div class="col-lg-8 nopadding-md">
      <div class="container nopadding-md" id="board-ctn">
        <div class="py-5" id="board">
          <div class="post-content mx-auto" id="post">
            
            <article class="markdown-body">
              <h1 id="Netty"><a href="#Netty" class="headerlink" title="Netty"></a>Netty</h1><h1 id="一、概述"><a href="#一、概述" class="headerlink" title="一、概述"></a>一、概述</h1><h2 id="1、什么是Netty"><a href="#1、什么是Netty" class="headerlink" title="1、什么是Netty"></a>1、什么是Netty</h2><pre><code class="hljs routeros">Netty is an asynchronous event-driven<span class="hljs-built_in"> network </span>application framework
<span class="hljs-keyword">for</span> rapid development of maintainable high performance protocol servers &amp; clients.</code></pre>

<p>Netty 是一个异步的、基于事件驱动的网络应用框架，用于快速开发可维护、高性能的网络服务器和客户端</p>
<p><strong>注意</strong>：<code>netty的异步还是基于多路复用的，并没有实现真正意义上的异步IO</code></p>
<h2 id="2、Netty的优势"><a href="#2、Netty的优势" class="headerlink" title="2、Netty的优势"></a>2、Netty的优势</h2><p>如果使用传统NIO，其工作量大，bug 多</p>
<ul>
<li>需要自己构建协议</li>
<li>解决 TCP 传输问题，如粘包、半包</li>
<li>因为bug的存在，epoll 空轮询导致 CPU 100%</li>
</ul>
<p>Netty 对 API 进行增强，使之更易用，如 </p>
<ul>
<li>FastThreadLocal =&gt; ThreadLocal</li>
<li>ByteBuf =&gt; ByteBuffer</li>
</ul>
<h1 id="二、入门案例"><a href="#二、入门案例" class="headerlink" title="二、入门案例"></a>二、入门案例</h1><h2 id="1、服务器端代码"><a href="#1、服务器端代码" class="headerlink" title="1、服务器端代码"></a>1、服务器端代码</h2><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloServer</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-comment">// 1、启动器，负责装配netty组件，启动服务器</span>
        <span class="hljs-keyword">new</span> ServerBootstrap()
                <span class="hljs-comment">// 2、创建 NioEventLoopGroup，可以简单理解为 线程池 + Selector</span>
                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup())
                <span class="hljs-comment">// 3、选择服务器的 ServerSocketChannel 实现</span>
                .channel(NioServerSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>
<span class="hljs-class">                // 4、<span class="hljs-title">child</span> 负责处理读写，该方法决定了 <span class="hljs-title">child</span> 执行哪些操作</span>
<span class="hljs-class">            	// <span class="hljs-title">ChannelInitializer</span> 处理器（仅执行一次）</span>
<span class="hljs-class">            	// 它的作用是待客户端<span class="hljs-title">SocketChannel</span>建立连接后，执行<span class="hljs-title">initChannel</span>以便添加更多的处理器</span>
<span class="hljs-class">                .<span class="hljs-title">childHandler</span>(<span class="hljs-title">new</span> <span class="hljs-title">ChannelInitializer</span>&lt;<span class="hljs-title">NioSocketChannel</span>&gt;() </span>&#123;
                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(NioSocketChannel nioSocketChannel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                        <span class="hljs-comment">// 5、SocketChannel的处理器，使用StringDecoder解码，ByteBuf=&gt;String</span>
                        nioSocketChannel.pipeline().addLast(<span class="hljs-keyword">new</span> StringDecoder());
                        <span class="hljs-comment">// 6、SocketChannel的业务处理，使用上一个处理器的处理结果</span>
                        nioSocketChannel.pipeline().addLast(<span class="hljs-keyword">new</span> SimpleChannelInboundHandler&lt;String&gt;() &#123;
                            <span class="hljs-meta">@Override</span>
                            <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext channelHandlerContext, String s)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                                System.out.println(s);
                            &#125;
                        &#125;);
                    &#125;
                    <span class="hljs-comment">// 7、ServerSocketChannel绑定8080端口</span>
                &#125;).bind(<span class="hljs-number">8080</span>);
    &#125;
&#125;</code></pre>



<h2 id="2、客户端代码"><a href="#2、客户端代码" class="headerlink" title="2、客户端代码"></a>2、客户端代码</h2><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloClient</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> InterruptedException </span>&#123;
        <span class="hljs-keyword">new</span> Bootstrap()
                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup())
                <span class="hljs-comment">// 选择客户 Socket 实现类，NioSocketChannel 表示基于 NIO 的客户端实现</span>
                .channel(NioSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>
<span class="hljs-class">                // <span class="hljs-title">ChannelInitializer</span> 处理器（仅执行一次）</span>
<span class="hljs-class">                // 它的作用是待客户端<span class="hljs-title">SocketChannel</span>建立连接后，执行<span class="hljs-title">initChannel</span>以便添加更多的处理器</span>
<span class="hljs-class">                .<span class="hljs-title">handler</span>(<span class="hljs-title">new</span> <span class="hljs-title">ChannelInitializer</span>&lt;<span class="hljs-title">Channel</span>&gt;() </span>&#123;
                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(Channel channel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                        <span class="hljs-comment">// 消息会经过通道 handler 处理，这里是将 String =&gt; ByteBuf 编码发出</span>
                        channel.pipeline().addLast(<span class="hljs-keyword">new</span> StringEncoder());
                    &#125;
                &#125;)
                <span class="hljs-comment">// 指定要连接的服务器和端口</span>
                .connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">"localhost"</span>, <span class="hljs-number">8080</span>))
                <span class="hljs-comment">// Netty 中很多方法都是异步的，如 connect</span>
                <span class="hljs-comment">// 这时需要使用 sync 方法等待 connect 建立连接完毕</span>
                .sync()
                <span class="hljs-comment">// 获取 channel 对象，它即为通道抽象，可以进行数据读写操作</span>
                .channel()
                <span class="hljs-comment">// 写入消息并清空缓冲区</span>
                .writeAndFlush(<span class="hljs-string">"hello world"</span>);
    &#125;
&#125;</code></pre>



<h2 id="3、运行流程"><a href="#3、运行流程" class="headerlink" title="3、运行流程"></a>3、运行流程</h2><p><strong>左：客户端        右：服务器端</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210420132155.png" srcset="/img/loading.gif" alt=""></p>
<h3 id="组件解释"><a href="#组件解释" class="headerlink" title="组件解释"></a>组件解释</h3><ul>
<li>channel 可以理解为数据的通道</li>
<li>msg 理解为流动的数据，最开始输入是 ByteBuf，但经过 pipeline 中的各个 handler 加工，会变成其它类型对象，最后输出又变成 ByteBuf</li>
<li>handler 可以理解为数据的处理工序<ul>
<li>工序有多道，<strong>合在一起就是 pipeline（传递途径）</strong>，pipeline 负责发布事件（读、读取完成…）传播给每个 handler， handler 对自己感兴趣的事件进行处理（重写了相应事件处理方法）<ul>
<li>pipeline 中有多个 handler，处理时会依次调用其中的 handler</li>
</ul>
</li>
<li>handler 分 Inbound 和 Outbound 两类<ul>
<li>Inbound 入站</li>
<li>Outbound 出站</li>
</ul>
</li>
</ul>
</li>
<li>eventLoop 可以理解为处理数据的工人<ul>
<li>eventLoop 可以管理多个 channel 的 io 操作，并且一旦 eventLoop 负责了某个 channel，就<strong>会将其与channel进行绑定</strong>，以后该 channel 中的 io 操作都由该 eventLoop 负责</li>
<li>eventLoop 既可以执行 io 操作，<strong>也可以进行任务处理</strong>，每个 eventLoop 有自己的任务队列，队列里可以堆放多个 channel 的待处理任务，任务分为普通任务、定时任务</li>
<li>eventLoop 按照 pipeline 顺序，依次按照 handler 的规划（代码）处理数据，可以为每个 handler 指定不同的 eventLoop</li>
</ul>
</li>
</ul>
<h1 id="三、组件"><a href="#三、组件" class="headerlink" title="三、组件"></a>三、组件</h1><h2 id="1、EventLoop"><a href="#1、EventLoop" class="headerlink" title="1、EventLoop"></a>1、EventLoop</h2><p><strong>事件循环对象</strong>  EventLoop</p>
<p>EventLoop 本质是一个<strong>单线程执行器</strong>（同时<strong>维护了一个 Selector</strong>），里面有 run 方法处理一个或多个 Channel 上源源不断的 io 事件</p>
<p>它的继承关系如下</p>
<ul>
<li>继承自 j.u.c.ScheduledExecutorService 因此包含了线程池中所有的方法</li>
<li>继承自 netty 自己的 OrderedEventExecutor<ul>
<li>提供了 boolean inEventLoop(Thread thread) 方法判断一个线程是否属于此 EventLoop</li>
<li>提供了 EventLoopGroup parent() 方法来看看自己属于哪个 EventLoopGroup</li>
</ul>
</li>
</ul>
<p><strong>事件循环组</strong> EventLoopGroup</p>
<p>EventLoopGroup 是一组 EventLoop，Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop，后续这个 Channel 上的 io 事件都由此 EventLoop 来处理（保证了 io 事件处理时的线程安全）</p>
<ul>
<li>继承自 netty 自己的 EventExecutorGroup<ul>
<li>实现了 Iterable 接口提供遍历 EventLoop 的能力</li>
<li>另有 next 方法获取集合中下一个 EventLoop</li>
</ul>
</li>
</ul>
<h3 id="处理普通与定时任务"><a href="#处理普通与定时任务" class="headerlink" title="处理普通与定时任务"></a>处理普通与定时任务</h3><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestEventLoop</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-comment">// 创建拥有两个EventLoop的NioEventLoopGroup，对应两个线程</span>
        EventLoopGroup group = <span class="hljs-keyword">new</span> NioEventLoopGroup(<span class="hljs-number">2</span>);
        <span class="hljs-comment">// 通过next方法可以获得下一个 EventLoop</span>
        System.out.println(group.next());
        System.out.println(group.next());

        <span class="hljs-comment">// 通过EventLoop执行普通任务</span>
        group.next().execute(()-&gt;&#123;
            System.out.println(Thread.currentThread().getName() + <span class="hljs-string">" hello"</span>);
        &#125;);

        <span class="hljs-comment">// 通过EventLoop执行定时任务</span>
        group.next().scheduleAtFixedRate(()-&gt;&#123;
            System.out.println(Thread.currentThread().getName() + <span class="hljs-string">" hello2"</span>);
        &#125;, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, TimeUnit.SECONDS);
        
        <span class="hljs-comment">// 优雅地关闭</span>
        group.shutdownGracefully();
    &#125;
&#125;</code></pre>

<p>输出结果如下</p>
<pre><code class="hljs angelscript">io.netty.channel.nio.<span class="hljs-symbol">NioEventLoop@</span><span class="hljs-number">7</span>bb11784
io.netty.channel.nio.<span class="hljs-symbol">NioEventLoop@</span><span class="hljs-number">33</span>a10788
nioEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-1</span> hello
nioEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-2</span> hello2
nioEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-2</span> hello2
nioEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-2</span> hello2</code></pre>

<p><strong>关闭 EventLoopGroup</strong></p>
<p>优雅关闭 <code>shutdownGracefully</code> 方法。该方法会首先切换 <code>EventLoopGroup</code> 到关闭状态从而拒绝新的任务的加入，然后在任务队列的任务都处理完成后，停止线程的运行。从而确保整体应用是在正常有序的状态下退出的</p>
<h3 id="处理IO任务"><a href="#处理IO任务" class="headerlink" title="处理IO任务"></a>处理IO任务</h3><h4 id="服务器代码"><a href="#服务器代码" class="headerlink" title="服务器代码"></a>服务器代码</h4><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyServer</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-keyword">new</span> ServerBootstrap()
                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup())
                .channel(NioServerSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>
<span class="hljs-class">                .<span class="hljs-title">childHandler</span>(<span class="hljs-title">new</span> <span class="hljs-title">ChannelInitializer</span>&lt;<span class="hljs-title">SocketChannel</span>&gt;() </span>&#123;
                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel socketChannel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                        socketChannel.pipeline().addLast(<span class="hljs-keyword">new</span> ChannelInboundHandlerAdapter() &#123;
                            <span class="hljs-meta">@Override</span>
                            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                                ByteBuf buf = (ByteBuf) msg;
                                System.out.println(Thread.currentThread().getName() + <span class="hljs-string">" "</span> + buf.toString(StandardCharsets.UTF_8));

                            &#125;
                        &#125;);
                    &#125;
                &#125;)
                .bind(<span class="hljs-number">8080</span>);
    &#125;
&#125;</code></pre>

<h4 id="客户端代码"><a href="#客户端代码" class="headerlink" title="客户端代码"></a>客户端代码</h4><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClient</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> IOException, InterruptedException </span>&#123;
        Channel channel = <span class="hljs-keyword">new</span> Bootstrap()
                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup())
                .channel(NioSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>
<span class="hljs-class">                .<span class="hljs-title">handler</span>(<span class="hljs-title">new</span> <span class="hljs-title">ChannelInitializer</span>&lt;<span class="hljs-title">SocketChannel</span>&gt;() </span>&#123;
                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel socketChannel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                        socketChannel.pipeline().addLast(<span class="hljs-keyword">new</span> StringEncoder());
                    &#125;
                &#125;)
                .connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">"localhost"</span>, <span class="hljs-number">8080</span>))
                .sync()
                .channel();
        System.out.println(channel);
        <span class="hljs-comment">// 此处打断点调试，调用 channel.writeAndFlush(...);</span>
        System.in.read();
    &#125;
&#125;</code></pre>



<h3 id="分工"><a href="#分工" class="headerlink" title="分工"></a>分工</h3><p>Bootstrap的group()方法<strong>可以传入两个EventLoopGroup参数</strong>，分别负责处理不同的事件</p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyServer</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-keyword">new</span> ServerBootstrap()
            	<span class="hljs-comment">// 两个Group，分别为Boss 负责Accept事件，Worker 负责读写事件</span>
                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup(<span class="hljs-number">1</span>), <span class="hljs-keyword">new</span> NioEventLoopGroup(<span class="hljs-number">2</span>))
            
				...
    &#125;
&#125;</code></pre>

<p>多个客户端分别发送 <code>hello</code>  结果</p>
<pre><code class="hljs java">nioEventLoopGroup-<span class="hljs-number">3</span>-<span class="hljs-number">1</span> hello1
nioEventLoopGroup-<span class="hljs-number">3</span>-<span class="hljs-number">2</span> hello2
nioEventLoopGroup-<span class="hljs-number">3</span>-<span class="hljs-number">1</span> hello3
nioEventLoopGroup-<span class="hljs-number">3</span>-<span class="hljs-number">2</span> hello4
nioEventLoopGroup-<span class="hljs-number">3</span>-<span class="hljs-number">2</span> hello4</code></pre>

<p>可以看出，一个EventLoop可以<strong>负责多个</strong>Channel，且EventLoop一旦与Channel绑定，则<strong>一直负责</strong>处理该Channel中的事件</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210421103251.png" srcset="/img/loading.gif" alt=""></p>
<h4 id="增加自定义EventLoopGroup"><a href="#增加自定义EventLoopGroup" class="headerlink" title="增加自定义EventLoopGroup"></a>增加自定义EventLoopGroup</h4><p>当有的<strong>任务需要较长的时间处理时，可以使用非NioEventLoopGroup</strong>，避免同一个NioEventLoop中的其他Channel在较长的时间内都无法得到处理</p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyServer</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-comment">// 增加自定义的非NioEventLoopGroup</span>
        EventLoopGroup group = <span class="hljs-keyword">new</span> DefaultEventLoopGroup();
        
        <span class="hljs-keyword">new</span> ServerBootstrap()
                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup(<span class="hljs-number">1</span>), <span class="hljs-keyword">new</span> NioEventLoopGroup(<span class="hljs-number">2</span>))
                .channel(NioServerSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>
<span class="hljs-class">                .<span class="hljs-title">childHandler</span>(<span class="hljs-title">new</span> <span class="hljs-title">ChannelInitializer</span>&lt;<span class="hljs-title">SocketChannel</span>&gt;() </span>&#123;
                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel socketChannel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                        <span class="hljs-comment">// 增加两个handler，第一个使用NioEventLoopGroup处理，第二个使用自定义EventLoopGroup处理</span>
                        socketChannel.pipeline().addLast(<span class="hljs-string">"nioHandler"</span>,<span class="hljs-keyword">new</span> ChannelInboundHandlerAdapter() &#123;
                            <span class="hljs-meta">@Override</span>
                            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                                ByteBuf buf = (ByteBuf) msg;
                                System.out.println(Thread.currentThread().getName() + <span class="hljs-string">" "</span> + buf.toString(StandardCharsets.UTF_8));
                                <span class="hljs-comment">// 调用下一个handler</span>
                                ctx.fireChannelRead(msg);
                            &#125;
                        &#125;)
                        <span class="hljs-comment">// 该handler绑定自定义的Group</span>
                        .addLast(group, <span class="hljs-string">"myHandler"</span>, <span class="hljs-keyword">new</span> ChannelInboundHandlerAdapter() &#123;
                            <span class="hljs-meta">@Override</span>
                            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                                ByteBuf buf = (ByteBuf) msg;
                                System.out.println(Thread.currentThread().getName() + <span class="hljs-string">" "</span> + buf.toString(StandardCharsets.UTF_8));
                            &#125;
                        &#125;);
                    &#125;
                &#125;)
                .bind(<span class="hljs-number">8080</span>);
    &#125;
&#125;</code></pre>

<p>启动四个客户端发送数据</p>
<pre><code class="hljs angelscript">nioEventLoopGroup<span class="hljs-number">-4</span><span class="hljs-number">-1</span> hello1
defaultEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-1</span> hello1
nioEventLoopGroup<span class="hljs-number">-4</span><span class="hljs-number">-2</span> hello2
defaultEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-2</span> hello2
nioEventLoopGroup<span class="hljs-number">-4</span><span class="hljs-number">-1</span> hello3
defaultEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-3</span> hello3
nioEventLoopGroup<span class="hljs-number">-4</span><span class="hljs-number">-2</span> hello4
defaultEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-4</span> hello4</code></pre>

<p>可以看出，客户端与服务器之间的事件，被nioEventLoopGroup和defaultEventLoopGroup分别处理</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210421103607.png" srcset="/img/loading.gif" alt=""></p>
<h4 id="切换的实现"><a href="#切换的实现" class="headerlink" title="切换的实现"></a>切换的实现</h4><p><strong>不同的EventLoopGroup切换的实现原理如下</strong></p>
<p>由上面的图可以看出，当handler中绑定的Group不同时，需要切换Group来执行不同的任务</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">invokeChannelRead</span><span class="hljs-params">(<span class="hljs-keyword">final</span> AbstractChannelHandlerContext next, Object msg)</span> </span>&#123;
    <span class="hljs-keyword">final</span> Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, <span class="hljs-string">"msg"</span>), next);
    <span class="hljs-comment">// 获得下一个EventLoop, excutor 即为 EventLoopGroup</span>
    EventExecutor executor = next.executor();
    
    <span class="hljs-comment">// 如果下一个EventLoop 在当前的 EventLoopGroup中</span>
    <span class="hljs-keyword">if</span> (executor.inEventLoop()) &#123;
        <span class="hljs-comment">// 使用当前 EventLoopGroup 中的 EventLoop 来处理任务</span>
        next.invokeChannelRead(m);
    &#125; <span class="hljs-keyword">else</span> &#123;
        <span class="hljs-comment">// 否则让另一个 EventLoopGroup 中的 EventLoop 来创建任务并执行</span>
        executor.execute(<span class="hljs-keyword">new</span> Runnable() &#123;
            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
                next.invokeChannelRead(m);
            &#125;
        &#125;);
    &#125;
&#125;</code></pre>

<ul>
<li>如果两个 handler 绑定的是<strong>同一个EventLoopGroup</strong>，那么就直接调用</li>
<li>否则，把要调用的代码封装为一个任务对象，由下一个 handler 的 EventLoopGroup 来调用</li>
</ul>
<h2 id="2、Channel"><a href="#2、Channel" class="headerlink" title="2、Channel"></a>2、Channel</h2><p>Channel 的常用方法</p>
<ul>
<li>close() 可以用来关闭Channel</li>
<li>closeFuture() 用来处理 Channel 的关闭<ul>
<li>sync 方法作用是同步等待 Channel 关闭</li>
<li>而 addListener 方法是异步等待 Channel 关闭</li>
</ul>
</li>
<li>pipeline() 方法用于添加处理器</li>
<li>write() 方法将数据写入<ul>
<li>因为缓冲机制，数据被写入到 Channel 中以后，不会立即被发送</li>
<li><strong>只有当缓冲满了或者调用了flush()方法后</strong>，才会将数据通过 Channel 发送出去</li>
</ul>
</li>
<li>writeAndFlush() 方法将数据写入并<strong>立即发送（刷出）</strong></li>
</ul>
<h3 id="ChannelFuture"><a href="#ChannelFuture" class="headerlink" title="ChannelFuture"></a>ChannelFuture</h3><h4 id="连接问题"><a href="#连接问题" class="headerlink" title="连接问题"></a>连接问题</h4><p><strong>拆分客户端代码</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClient</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> IOException, InterruptedException </span>&#123;
        ChannelFuture channelFuture = <span class="hljs-keyword">new</span> Bootstrap()
                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup())
                .channel(NioSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>
<span class="hljs-class">                .<span class="hljs-title">handler</span>(<span class="hljs-title">new</span> <span class="hljs-title">ChannelInitializer</span>&lt;<span class="hljs-title">SocketChannel</span>&gt;() </span>&#123;
                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel socketChannel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                        socketChannel.pipeline().addLast(<span class="hljs-keyword">new</span> StringEncoder());
                    &#125;
                &#125;)
                <span class="hljs-comment">// 该方法为异步非阻塞方法，主线程调用后不会被阻塞，真正去执行连接操作的是NIO线程</span>
            	<span class="hljs-comment">// NIO线程：NioEventLoop 中的线程</span>
                .connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">"localhost"</span>, <span class="hljs-number">8080</span>));
        
        <span class="hljs-comment">// 该方法用于等待连接真正建立</span>
        channelFuture.sync();
        
        <span class="hljs-comment">// 获取客户端-服务器之间的Channel对象</span>
        Channel channel = channelFuture.channel();
        channel.writeAndFlush(<span class="hljs-string">"hello world"</span>);
        System.in.read();
    &#125;
&#125;</code></pre>

<p>如果我们去掉<code>channelFuture.sync()</code>方法，会服务器无法收到<code>hello world</code></p>
<p>这是因为建立连接(connect)的过程是<strong>异步非阻塞</strong>的，若不通过<code>sync()</code>方法阻塞主线程，等待连接真正建立，这时通过 channelFuture.channel() <strong>拿到的 Channel 对象，并不是真正与服务器建立好连接的 Channel</strong>，也就没法将信息正确的传输给服务器端</p>
<p>所以需要通过<code>channelFuture.sync()</code>方法，阻塞主线程，<strong>同步处理结果</strong>，等待连接真正建立好以后，再去获得 Channel 传递数据。使用该方法，获取 Channel 和发送数据的线程<strong>都是主线程</strong></p>
<p>下面还有一种方法，用于<strong>异步</strong>获取建立连接后的 Channel 和发送数据，使得执行这些操作的线程是 NIO 线程（去执行connect操作的线程）</p>
<p><strong>addListener方法</strong></p>
<p>通过这种方法可以<strong>在NIO线程中获取 Channel 并发送数据</strong>，而不是在主线程中执行这些操作</p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClient</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> IOException, InterruptedException </span>&#123;
        ChannelFuture channelFuture = <span class="hljs-keyword">new</span> Bootstrap()
                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup())
                .channel(NioSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>
<span class="hljs-class">                .<span class="hljs-title">handler</span>(<span class="hljs-title">new</span> <span class="hljs-title">ChannelInitializer</span>&lt;<span class="hljs-title">SocketChannel</span>&gt;() </span>&#123;
                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel socketChannel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                        socketChannel.pipeline().addLast(<span class="hljs-keyword">new</span> StringEncoder());
                    &#125;
                &#125;)
                <span class="hljs-comment">// 该方法为异步非阻塞方法，主线程调用后不会被阻塞，真正去执行连接操作的是NIO线程</span>
                <span class="hljs-comment">// NIO线程：NioEventLoop 中的线程</span>
                .connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">"localhost"</span>, <span class="hljs-number">8080</span>));
        
		<span class="hljs-comment">// 当connect方法执行完毕后，也就是连接真正建立后</span>
        <span class="hljs-comment">// 会在NIO线程中调用operationComplete方法</span>
        channelFuture.addListener(<span class="hljs-keyword">new</span> ChannelFutureListener() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">operationComplete</span><span class="hljs-params">(ChannelFuture channelFuture)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                Channel channel = channelFuture.channel();
                channel.writeAndFlush(<span class="hljs-string">"hello world"</span>);
            &#125;
        &#125;);
        System.in.read();
    &#125;
&#125;</code></pre>



<h4 id="处理关闭"><a href="#处理关闭" class="headerlink" title="处理关闭"></a>处理关闭</h4><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ReadClient</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> InterruptedException </span>&#123;
        <span class="hljs-comment">// 创建EventLoopGroup，使用完毕后关闭</span>
        NioEventLoopGroup group = <span class="hljs-keyword">new</span> NioEventLoopGroup();
        
        ChannelFuture channelFuture = <span class="hljs-keyword">new</span> Bootstrap()
                .group(group)
                .channel(NioSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>
<span class="hljs-class">                .<span class="hljs-title">handler</span>(<span class="hljs-title">new</span> <span class="hljs-title">ChannelInitializer</span>&lt;<span class="hljs-title">SocketChannel</span>&gt;() </span>&#123;
                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel socketChannel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                        socketChannel.pipeline().addLast(<span class="hljs-keyword">new</span> StringEncoder());
                    &#125;
                &#125;)
                .connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">"localhost"</span>, <span class="hljs-number">8080</span>));
        channelFuture.sync();

        Channel channel = channelFuture.channel();
        Scanner scanner = <span class="hljs-keyword">new</span> Scanner(System.in);

        <span class="hljs-comment">// 创建一个线程用于输入并向服务器发送</span>
        <span class="hljs-keyword">new</span> Thread(()-&gt;&#123;
            <span class="hljs-keyword">while</span> (<span class="hljs-keyword">true</span>) &#123;
                String msg = scanner.next();
                <span class="hljs-keyword">if</span> (<span class="hljs-string">"q"</span>.equals(msg)) &#123;
                    <span class="hljs-comment">// 关闭操作是异步的，在NIO线程中执行</span>
                    channel.close();
                    <span class="hljs-keyword">break</span>;
                &#125;
                channel.writeAndFlush(msg);
            &#125;
        &#125;, <span class="hljs-string">"inputThread"</span>).start();

        <span class="hljs-comment">// 获得closeFuture对象</span>
        ChannelFuture closeFuture = channel.closeFuture();
        System.out.println(<span class="hljs-string">"waiting close..."</span>);
        
        <span class="hljs-comment">// 同步等待NIO线程执行完close操作</span>
        closeFuture.sync();
        
        <span class="hljs-comment">// 关闭之后执行一些操作，可以保证执行的操作一定是在channel关闭以后执行的</span>
        System.out.println(<span class="hljs-string">"关闭之后执行一些额外操作..."</span>);
        
        <span class="hljs-comment">// 关闭EventLoopGroup</span>
        group.shutdownGracefully();
    &#125;
&#125;</code></pre>



<p><strong>关闭channel</strong></p>
<p>当我们要关闭channel时，可以调用channel.close()方法进行关闭。但是该方法也是一个<strong>异步方法</strong>。真正的关闭操作并不是在调用该方法的线程中执行的，而是<strong>在NIO线程中执行真正的关闭操作</strong></p>
<p>如果我们想在channel<strong>真正关闭以后</strong>，执行一些额外的操作，可以选择以下两种方法来实现</p>
<ul>
<li><p>通过channel.closeFuture()方法获得对应的ChannelFuture对象，然后调用<strong>sync()方法</strong>阻塞执行操作的线程，等待channel真正关闭后，再执行其他操作</p>
<pre><code class="hljs java"><span class="hljs-comment">// 获得closeFuture对象</span>
ChannelFuture closeFuture = channel.closeFuture();

<span class="hljs-comment">// 同步等待NIO线程执行完close操作</span>
closeFuture.sync();</code></pre>
</li>
<li><p>调用<strong>closeFuture.addListener</strong>方法，添加close的后续操作</p>
<pre><code class="hljs java">closeFuture.addListener(<span class="hljs-keyword">new</span> ChannelFutureListener() &#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">operationComplete</span><span class="hljs-params">(ChannelFuture channelFuture)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        <span class="hljs-comment">// 等待channel关闭后才执行的操作</span>
        System.out.println(<span class="hljs-string">"关闭之后执行一些额外操作..."</span>);
        <span class="hljs-comment">// 关闭EventLoopGroup</span>
        group.shutdownGracefully();
    &#125;
&#125;);</code></pre>



</li>
</ul>
<h2 id="3、Future与Promise"><a href="#3、Future与Promise" class="headerlink" title="3、Future与Promise"></a>3、Future与Promise</h2><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><p>netty 中的 Future 与 jdk 中的 Future <strong>同名</strong>，但是是两个接口</p>
<p>netty 的 Future 继承自 jdk 的 Future，而 Promise 又对 netty Future 进行了扩展</p>
<ul>
<li>jdk Future 只能同步等待任务结束（或成功、或失败）才能得到结果</li>
<li>netty Future 可以同步等待任务结束得到结果，也可以异步方式得到结果，但<strong>都是要等任务结束</strong></li>
<li>netty Promise 不仅有 netty Future 的功能，而且脱离了任务独立存在，<strong>只作为两个线程间传递结果的容器</strong></li>
</ul>
<table>
<thead>
<tr>
<th>功能/名称</th>
<th>jdk Future</th>
<th>netty Future</th>
<th>Promise</th>
</tr>
</thead>
<tbody><tr>
<td>cancel</td>
<td>取消任务</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>isCanceled</td>
<td>任务是否取消</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>isDone</td>
<td>任务是否完成，不能区分成功失败</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>get</td>
<td>获取任务结果，阻塞等待</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>getNow</td>
<td>-</td>
<td>获取任务结果，非阻塞，还未产生结果时返回 null</td>
<td>-</td>
</tr>
<tr>
<td>await</td>
<td>-</td>
<td>等待任务结束，如果任务失败，<strong>不会抛异常</strong>，而是通过 isSuccess 判断</td>
<td>-</td>
</tr>
<tr>
<td>sync</td>
<td>-</td>
<td>等待任务结束，如果任务失败，抛出异常</td>
<td>-</td>
</tr>
<tr>
<td>isSuccess</td>
<td>-</td>
<td>判断任务是否成功</td>
<td>-</td>
</tr>
<tr>
<td>cause</td>
<td>-</td>
<td>获取失败信息，非阻塞，如果没有失败，返回null</td>
<td>-</td>
</tr>
<tr>
<td>addLinstener</td>
<td>-</td>
<td>添加回调，异步接收结果</td>
<td>-</td>
</tr>
<tr>
<td>setSuccess</td>
<td>-</td>
<td>-</td>
<td>设置成功结果</td>
</tr>
<tr>
<td>setFailure</td>
<td>-</td>
<td>-</td>
<td>设置失败结果</td>
</tr>
</tbody></table>
<h3 id="JDK-Future"><a href="#JDK-Future" class="headerlink" title="JDK Future"></a>JDK Future</h3><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JdkFuture</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> ExecutionException, InterruptedException </span>&#123;
        ThreadFactory factory = <span class="hljs-keyword">new</span> ThreadFactory() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> Thread <span class="hljs-title">newThread</span><span class="hljs-params">(Runnable r)</span> </span>&#123;
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Thread(r, <span class="hljs-string">"JdkFuture"</span>);
            &#125;
        &#125;;
        <span class="hljs-comment">// 创建线程池</span>
        ThreadPoolExecutor executor = <span class="hljs-keyword">new</span> ThreadPoolExecutor(<span class="hljs-number">5</span>, <span class="hljs-number">10</span>,<span class="hljs-number">10</span>, TimeUnit.SECONDS, <span class="hljs-keyword">new</span> ArrayBlockingQueue&lt;&gt;(<span class="hljs-number">10</span>), factory);

        <span class="hljs-comment">// 获得Future对象</span>
        Future&lt;Integer&gt; future = executor.submit(<span class="hljs-keyword">new</span> Callable&lt;Integer&gt;() &#123;

            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> Integer <span class="hljs-title">call</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                TimeUnit.SECONDS.sleep(<span class="hljs-number">1</span>);
                <span class="hljs-keyword">return</span> <span class="hljs-number">50</span>;
            &#125;
        &#125;);

        <span class="hljs-comment">// 通过阻塞的方式，获得运行结果</span>
        System.out.println(future.get());
    &#125;
&#125;</code></pre>



<h3 id="Netty-Future"><a href="#Netty-Future" class="headerlink" title="Netty Future"></a>Netty Future</h3><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NettyFuture</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> ExecutionException, InterruptedException </span>&#123;
        NioEventLoopGroup group = <span class="hljs-keyword">new</span> NioEventLoopGroup();

        <span class="hljs-comment">// 获得 EventLoop 对象</span>
        EventLoop eventLoop = group.next();
        Future&lt;Integer&gt; future = eventLoop.submit(<span class="hljs-keyword">new</span> Callable&lt;Integer&gt;() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> Integer <span class="hljs-title">call</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                <span class="hljs-keyword">return</span> <span class="hljs-number">50</span>;
            &#125;
        &#125;);

        <span class="hljs-comment">// 主线程中获取结果</span>
        System.out.println(Thread.currentThread().getName() + <span class="hljs-string">" 获取结果"</span>);
        System.out.println(<span class="hljs-string">"getNow "</span> + future.getNow());
        System.out.println(<span class="hljs-string">"get "</span> + future.get());

        <span class="hljs-comment">// NIO线程中异步获取结果</span>
        future.addListener(<span class="hljs-keyword">new</span> GenericFutureListener&lt;Future&lt;? <span class="hljs-keyword">super</span> Integer&gt;&gt;() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">operationComplete</span><span class="hljs-params">(Future&lt;? <span class="hljs-keyword">super</span> Integer&gt; future)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                System.out.println(Thread.currentThread().getName() + <span class="hljs-string">" 获取结果"</span>);
                System.out.println(<span class="hljs-string">"getNow "</span> + future.getNow());
            &#125;
        &#125;);
    &#125;
&#125;</code></pre>

<p><strong>运行结果</strong></p>
<pre><code class="hljs angelscript">main 获取结果
getNow <span class="hljs-literal">null</span>
<span class="hljs-keyword">get</span> <span class="hljs-number">50</span>
nioEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-1</span> 获取结果
getNow <span class="hljs-number">50</span></code></pre>



<p>Netty中的Future对象，可以通过EventLoop的sumbit()方法得到</p>
<ul>
<li>可以通过Future对象的<strong>get方法</strong>，阻塞地获取返回结果</li>
<li>也可以通过<strong>getNow方法</strong>，获取结果，若还没有结果，则返回null，该方法是非阻塞的</li>
<li>还可以通过<strong>future.addListener方法</strong>，在Callable方法执行的线程中，异步获取返回结果</li>
</ul>
<h3 id="Netty-Promise"><a href="#Netty-Promise" class="headerlink" title="Netty Promise"></a>Netty Promise</h3><p>Promise相当于一个容器，可以用于存放各个线程中的结果，然后让其他线程去获取该结果</p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NettyPromise</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> ExecutionException, InterruptedException </span>&#123;
        <span class="hljs-comment">// 创建EventLoop</span>
        NioEventLoopGroup group = <span class="hljs-keyword">new</span> NioEventLoopGroup();
        EventLoop eventLoop = group.next();

        <span class="hljs-comment">// 创建Promise对象，用于存放结果</span>
        DefaultPromise&lt;Integer&gt; promise = <span class="hljs-keyword">new</span> DefaultPromise&lt;&gt;(eventLoop);

        <span class="hljs-keyword">new</span> Thread(()-&gt;&#123;
            <span class="hljs-keyword">try</span> &#123;
                TimeUnit.SECONDS.sleep(<span class="hljs-number">1</span>);
            &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;
                e.printStackTrace();
            &#125;
            <span class="hljs-comment">// 自定义线程向Promise中存放结果</span>
            promise.setSuccess(<span class="hljs-number">50</span>);
        &#125;).start();

        <span class="hljs-comment">// 主线程从Promise中获取结果</span>
        System.out.println(Thread.currentThread().getName() + <span class="hljs-string">" "</span> + promise.get());
    &#125;
&#125;</code></pre>



<h2 id="4、Handler与Pipeline"><a href="#4、Handler与Pipeline" class="headerlink" title="4、Handler与Pipeline"></a>4、Handler与Pipeline</h2><h3 id="Pipeline"><a href="#Pipeline" class="headerlink" title="Pipeline"></a>Pipeline</h3><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PipeLineServer</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-keyword">new</span> ServerBootstrap()
                .group(<span class="hljs-keyword">new</span> NioEventLoopGroup())
                .channel(NioServerSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>
<span class="hljs-class">                .<span class="hljs-title">childHandler</span>(<span class="hljs-title">new</span> <span class="hljs-title">ChannelInitializer</span>&lt;<span class="hljs-title">SocketChannel</span>&gt;() </span>&#123;
                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel socketChannel)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                        <span class="hljs-comment">// 在socketChannel的pipeline中添加handler</span>
                        <span class="hljs-comment">// pipeline中handler是带有head与tail节点的双向链表，的实际结构为</span>
    				 	<span class="hljs-comment">// head &lt;-&gt; handler1 &lt;-&gt; ... &lt;-&gt; handler4 &lt;-&gt;tail</span>
                        <span class="hljs-comment">// Inbound主要处理入站操作，一般为读操作，发生入站操作时会触发Inbound方法</span>
                        <span class="hljs-comment">// 入站时，handler是从head向后调用的</span>
                        socketChannel.pipeline().addLast(<span class="hljs-string">"handler1"</span> ,<span class="hljs-keyword">new</span> ChannelInboundHandlerAdapter() &#123;
                            <span class="hljs-meta">@Override</span>
                            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                                System.out.println(Thread.currentThread().getName() + <span class="hljs-string">" Inbound handler 1"</span>);
                                <span class="hljs-comment">// 父类该方法内部会调用fireChannelRead</span>
                                <span class="hljs-comment">// 将数据传递给下一个handler</span>
                                <span class="hljs-keyword">super</span>.channelRead(ctx, msg);
                            &#125;
                        &#125;);
                        socketChannel.pipeline().addLast(<span class="hljs-string">"handler2"</span>, <span class="hljs-keyword">new</span> ChannelInboundHandlerAdapter() &#123;
                            <span class="hljs-meta">@Override</span>
                            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                                System.out.println(Thread.currentThread().getName() + <span class="hljs-string">" Inbound handler 2"</span>);
                                <span class="hljs-comment">// 执行write操作，使得Outbound的方法能够得到调用</span>
          socketChannel.writeAndFlush(ctx.alloc().buffer().writeBytes(<span class="hljs-string">"Server..."</span>.getBytes(StandardCharsets.UTF_8)));
                                <span class="hljs-keyword">super</span>.channelRead(ctx, msg);
                            &#125;
                        &#125;);
                        <span class="hljs-comment">// Outbound主要处理出站操作，一般为写操作，发生出站操作时会触发Outbound方法</span>
                        <span class="hljs-comment">// 出站时，handler的调用是从tail向前调用的</span>
                        socketChannel.pipeline().addLast(<span class="hljs-string">"handler3"</span> ,<span class="hljs-keyword">new</span> ChannelOutboundHandlerAdapter()&#123;
                            <span class="hljs-meta">@Override</span>
                            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">write</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                                System.out.println(Thread.currentThread().getName() + <span class="hljs-string">" Outbound handler 1"</span>);
                                <span class="hljs-keyword">super</span>.write(ctx, msg, promise);
                            &#125;
                        &#125;);
                        socketChannel.pipeline().addLast(<span class="hljs-string">"handler4"</span> ,<span class="hljs-keyword">new</span> ChannelOutboundHandlerAdapter()&#123;
                            <span class="hljs-meta">@Override</span>
                            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">write</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                                System.out.println(Thread.currentThread().getName() + <span class="hljs-string">" Outbound handler 2"</span>);
                                <span class="hljs-keyword">super</span>.write(ctx, msg, promise);
                            &#125;
                        &#125;);
                    &#125;
                &#125;)
                .bind(<span class="hljs-number">8080</span>);
    &#125;
&#125;</code></pre>

<p><strong>运行结果如下</strong></p>
<pre><code class="hljs angelscript">nioEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-2</span> Inbound handler <span class="hljs-number">1</span>
nioEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-2</span> Inbound handler <span class="hljs-number">2</span>
nioEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-2</span> Outbound handler <span class="hljs-number">2</span>
nioEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-2</span> Outbound handler <span class="hljs-number">1</span></code></pre>

<p>通过channel.pipeline().addLast(name, handler)添加handler时，<strong>记得给handler取名字</strong>。这样可以调用pipeline的<strong>addAfter、addBefore等方法更灵活地向pipeline中添加handler</strong></p>
<p>handler需要放入通道的pipeline中，才能根据放入顺序来使用handler</p>
<ul>
<li>pipeline是结构是一个带有head与tail指针的双向链表，其中的节点为handler<ul>
<li>要通过ctx.fireChannelRead(msg)等方法，<strong>将当前handler的处理结果传递给下一个handler</strong></li>
</ul>
</li>
<li>当有<strong>入站</strong>（Inbound）操作时，会从<strong>head开始向后</strong>调用handler，直到handler不是处理Inbound操作为止</li>
<li>当有<strong>出站</strong>（Outbound）操作时，会从<strong>tail开始向前</strong>调用handler，直到handler不是处理Outbound操作为止</li>
</ul>
<p><strong>具体结构如下</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210423102354.png" srcset="/img/loading.gif" alt=""></p>
<p><strong>调用顺序如下</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210423105200.png" srcset="/img/loading.gif" alt=""></p>
<h3 id="OutboundHandler"><a href="#OutboundHandler" class="headerlink" title="OutboundHandler"></a>OutboundHandler</h3><h4 id="socketChannel-writeAndFlush"><a href="#socketChannel-writeAndFlush" class="headerlink" title="socketChannel.writeAndFlush()"></a>socketChannel.writeAndFlush()</h4><p>当handler中调用该方法进行写操作时，会触发Outbound操作，<strong>此时是从tail向前寻找OutboundHandler</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210423122010.png" srcset="/img/loading.gif" alt=""></p>
<h4 id="ctx-writeAndFlush"><a href="#ctx-writeAndFlush" class="headerlink" title="ctx.writeAndFlush()"></a>ctx.writeAndFlush()</h4><p>当handler中调用该方法进行写操作时，会触发Outbound操作，<strong>此时是从当前handler向前寻找OutboundHandler</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210423122050.png" srcset="/img/loading.gif" alt=""></p>
<h3 id="EmbeddedChannel"><a href="#EmbeddedChannel" class="headerlink" title="EmbeddedChannel"></a>EmbeddedChannel</h3><p>EmbeddedChannel可以用于测试各个handler，通过其构造函数按顺序传入需要测试handler，然后调用对应的Inbound和Outbound方法即可</p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestEmbeddedChannel</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        ChannelInboundHandlerAdapter h1 = <span class="hljs-keyword">new</span> ChannelInboundHandlerAdapter() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                System.out.println(<span class="hljs-string">"1"</span>);
                <span class="hljs-keyword">super</span>.channelRead(ctx, msg);
            &#125;
        &#125;;

        ChannelInboundHandlerAdapter h2 = <span class="hljs-keyword">new</span> ChannelInboundHandlerAdapter() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                System.out.println(<span class="hljs-string">"2"</span>);
                <span class="hljs-keyword">super</span>.channelRead(ctx, msg);
            &#125;
        &#125;;

        ChannelOutboundHandlerAdapter h3 = <span class="hljs-keyword">new</span> ChannelOutboundHandlerAdapter() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">write</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                System.out.println(<span class="hljs-string">"3"</span>);
                <span class="hljs-keyword">super</span>.write(ctx, msg, promise);
            &#125;
        &#125;;

        ChannelOutboundHandlerAdapter h4 = <span class="hljs-keyword">new</span> ChannelOutboundHandlerAdapter() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">write</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                System.out.println(<span class="hljs-string">"4"</span>);
                <span class="hljs-keyword">super</span>.write(ctx, msg, promise);
            &#125;
        &#125;;

        <span class="hljs-comment">// 用于测试Handler的Channel</span>
        EmbeddedChannel channel = <span class="hljs-keyword">new</span> EmbeddedChannel(h1, h2, h3, h4);
        
        <span class="hljs-comment">// 执行Inbound操作 </span>
        channel.writeInbound(ByteBufAllocator.DEFAULT.buffer().writeBytes(<span class="hljs-string">"hello"</span>.getBytes(StandardCharsets.UTF_8)));
        <span class="hljs-comment">// 执行Outbound操作</span>
        channel.writeOutbound(ByteBufAllocator.DEFAULT.buffer().writeBytes(<span class="hljs-string">"hello"</span>.getBytes(StandardCharsets.UTF_8)));
    &#125;
&#125;</code></pre>



<h2 id="5、ByteBuf"><a href="#5、ByteBuf" class="headerlink" title="5、ByteBuf"></a>5、ByteBuf</h2><p><strong>调试工具方法</strong></p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">log</span><span class="hljs-params">(ByteBuf buffer)</span> </span>&#123;
    <span class="hljs-keyword">int</span> length = buffer.readableBytes();
    <span class="hljs-keyword">int</span> rows = length / <span class="hljs-number">16</span> + (length % <span class="hljs-number">15</span> == <span class="hljs-number">0</span> ? <span class="hljs-number">0</span> : <span class="hljs-number">1</span>) + <span class="hljs-number">4</span>;
    StringBuilder buf = <span class="hljs-keyword">new</span> StringBuilder(rows * <span class="hljs-number">80</span> * <span class="hljs-number">2</span>)
        .append(<span class="hljs-string">"read index:"</span>).append(buffer.readerIndex())
        .append(<span class="hljs-string">" write index:"</span>).append(buffer.writerIndex())
        .append(<span class="hljs-string">" capacity:"</span>).append(buffer.capacity())
        .append(NEWLINE);
    appendPrettyHexDump(buf, buffer);
    System.out.println(buf.toString());
&#125;</code></pre>

<p>该方法可以帮助我们更为详细地查看ByteBuf中的内容</p>
<h3 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h3><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ByteBufStudy</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-comment">// 创建ByteBuf</span>
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(<span class="hljs-number">16</span>);
        ByteBufUtil.log(buffer);

        <span class="hljs-comment">// 向buffer中写入数据</span>
        StringBuilder sb = <span class="hljs-keyword">new</span> StringBuilder();
        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">20</span>; i++) &#123;
            sb.append(<span class="hljs-string">"a"</span>);
        &#125;
        buffer.writeBytes(sb.toString().getBytes(StandardCharsets.UTF_8));

        <span class="hljs-comment">// 查看写入结果</span>
        ByteBufUtil.log(buffer);
    &#125;
&#125;</code></pre>

<p><strong>运行结果</strong></p>
<pre><code class="hljs angelscript">read index:<span class="hljs-number">0</span> write index:<span class="hljs-number">0</span> capacity:<span class="hljs-number">16</span>

read index:<span class="hljs-number">0</span> write index:<span class="hljs-number">20</span> capacity:<span class="hljs-number">64</span>
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> |aaaaaaaaaaaaaaaa|
|<span class="hljs-number">00000010</span>| <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span>                                     |aaaa            |
+--------+-------------------------------------------------+----------------+</code></pre>



<p>ByteBuf<strong>通过<code>ByteBufAllocator</code>选择allocator并调用对应的buffer()方法来创建的</strong>，默认使用<strong>直接内存</strong>作为ByteBuf，容量为256个字节，可以指定初始容量的大小</p>
<p>当ByteBuf的容量无法容纳所有数据时，<strong>ByteBuf会进行扩容操作</strong></p>
<p><strong>如果在handler中创建ByteBuf，建议使用<code>ChannelHandlerContext ctx.alloc().buffer()</code>来创建</strong></p>
<h3 id="直接内存与堆内存"><a href="#直接内存与堆内存" class="headerlink" title="直接内存与堆内存"></a>直接内存与堆内存</h3><p>通过该方法创建的ByteBuf，使用的是<strong>基于直接内存</strong>的ByteBuf</p>
<pre><code class="hljs java">ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(<span class="hljs-number">16</span>);</code></pre>

<p>可以使用下面的代码来创建池化<strong>基于堆</strong>的 ByteBuf</p>
<pre><code class="hljs java">ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(<span class="hljs-number">16</span>);</code></pre>

<p>也可以使用下面的代码来创建池化<strong>基于直接内存</strong>的 ByteBuf</p>
<pre><code class="hljs java">ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(<span class="hljs-number">16</span>);</code></pre>

<ul>
<li>直接内存创建和销毁的代价昂贵，但读写性能高（少一次内存复制），适合配合池化功能一起用</li>
<li>直接内存对 GC 压力小，因为这部分内存不受 JVM 垃圾回收的管理，但也要注意及时主动释放</li>
</ul>
<p><strong>验证</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ByteBufStudy</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(<span class="hljs-number">16</span>);
        System.out.println(buffer.getClass());

        buffer = ByteBufAllocator.DEFAULT.heapBuffer(<span class="hljs-number">16</span>);
        System.out.println(buffer.getClass());

        buffer = ByteBufAllocator.DEFAULT.directBuffer(<span class="hljs-number">16</span>);
        System.out.println(buffer.getClass());
    &#125;
&#125;</code></pre>

<pre><code class="hljs stylus"><span class="hljs-comment">// 使用池化的直接内存</span>
class io<span class="hljs-selector-class">.netty</span><span class="hljs-selector-class">.buffer</span>.PooledUnsafeDirectByteBuf
    
<span class="hljs-comment">// 使用池化的堆内存    </span>
class io<span class="hljs-selector-class">.netty</span><span class="hljs-selector-class">.buffer</span>.PooledUnsafeHeapByteBuf
    
<span class="hljs-comment">// 使用池化的直接内存    </span>
class io<span class="hljs-selector-class">.netty</span><span class="hljs-selector-class">.buffer</span>.PooledUnsafeDirectByteBuf</code></pre>



<h3 id="池化与非池化"><a href="#池化与非池化" class="headerlink" title="池化与非池化"></a>池化与非池化</h3><p>池化的最大意义在于可以<strong>重用</strong> ByteBuf，优点有</p>
<ul>
<li>没有池化，则每次都得创建新的 ByteBuf 实例，这个操作对直接内存代价昂贵，就算是堆内存，也会增加 GC 压力</li>
<li>有了池化，则可以重用池中 ByteBuf 实例，并且采用了与 jemalloc 类似的内存分配算法提升分配效率</li>
<li>高并发时，池化功能更节约内存，减少内存溢出的可能</li>
</ul>
<p>池化功能是否开启，可以通过下面的系统环境变量来设置</p>
<pre><code class="hljs java">-Dio.netty.allocator.type=&#123;unpooled|pooled&#125;</code></pre>

<ul>
<li>4.1 以后，<strong>非 Android 平台默认启用池化实现</strong>，Android 平台启用非池化实现</li>
<li>4.1 之前，池化功能还不成熟，默认是非池化实现</li>
</ul>
<h3 id="组成"><a href="#组成" class="headerlink" title="组成"></a>组成</h3><p>ByteBuf主要有以下几个组成部分</p>
<ul>
<li>最大容量与当前容量<ul>
<li>在构造ByteBuf时，可传入两个参数，分别代表初始容量和最大容量，若未传入第二个参数（最大容量），最大容量默认为Integer.MAX_VALUE</li>
<li>当ByteBuf容量无法容纳所有数据时，会进行扩容操作，若<strong>超出最大容量</strong>，会抛出<code>java.lang.IndexOutOfBoundsException</code>异常</li>
</ul>
</li>
<li>读写操作不同于ByteBuffer只用position进行控制，<strong>ByteBuf分别由读指针和写指针两个指针控制</strong>。进行读写操作时，无需进行模式的切换<ul>
<li>读指针前的部分被称为废弃部分，是已经读过的内容</li>
<li>读指针与写指针之间的空间称为可读部分</li>
<li>写指针与当前容量之间的空间称为可写部分</li>
</ul>
</li>
</ul>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210423143030.png" srcset="/img/loading.gif" alt=""></p>
<h3 id="写入"><a href="#写入" class="headerlink" title="写入"></a>写入</h3><p>常用方法如下</p>
<table>
<thead>
<tr>
<th>方法签名</th>
<th>含义</th>
<th>备注</th>
</tr>
</thead>
<tbody><tr>
<td>writeBoolean(boolean value)</td>
<td>写入 boolean 值</td>
<td><strong>用一字节 01|00 代表 true|false</strong></td>
</tr>
<tr>
<td>writeByte(int value)</td>
<td>写入 byte 值</td>
<td></td>
</tr>
<tr>
<td>writeShort(int value)</td>
<td>写入 short 值</td>
<td></td>
</tr>
<tr>
<td>writeInt(int value)</td>
<td>写入 int 值</td>
<td>Big Endian（大端写入），即 0x250，写入后 00 00 02 50</td>
</tr>
<tr>
<td>writeIntLE(int value)</td>
<td>写入 int 值</td>
<td>Little Endian（小端写入），即 0x250，写入后 50 02 00 00</td>
</tr>
<tr>
<td>writeLong(long value)</td>
<td>写入 long 值</td>
<td></td>
</tr>
<tr>
<td>writeChar(int value)</td>
<td>写入 char 值</td>
<td></td>
</tr>
<tr>
<td>writeFloat(float value)</td>
<td>写入 float 值</td>
<td></td>
</tr>
<tr>
<td>writeDouble(double value)</td>
<td>写入 double 值</td>
<td></td>
</tr>
<tr>
<td>writeBytes(ByteBuf src)</td>
<td>写入 netty 的 ByteBuf</td>
<td></td>
</tr>
<tr>
<td>writeBytes(byte[] src)</td>
<td>写入 byte[]</td>
<td></td>
</tr>
<tr>
<td>writeBytes(ByteBuffer src)</td>
<td>写入 nio 的 <strong>ByteBuffer</strong></td>
<td></td>
</tr>
<tr>
<td>int writeCharSequence(CharSequence sequence, Charset charset)</td>
<td>写入字符串</td>
<td>CharSequence为字符串类的父类，第二个参数为对应的字符集</td>
</tr>
</tbody></table>
<blockquote>
<p>注意</p>
<ul>
<li>这些方法的未指明返回值的，其返回值都是 ByteBuf，意味着可以链式调用来写入不同的数据</li>
<li>网络传输中，<strong>默认习惯是 Big Endian</strong>，使用 writeInt(int value)</li>
</ul>
</blockquote>
<p><strong>使用方法</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ByteBufStudy</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-comment">// 创建ByteBuf</span>
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(<span class="hljs-number">16</span>, <span class="hljs-number">20</span>);
        ByteBufUtil.log(buffer);

        <span class="hljs-comment">// 向buffer中写入数据</span>
        buffer.writeBytes(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]&#123;<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>&#125;);
        ByteBufUtil.log(buffer);

        buffer.writeInt(<span class="hljs-number">5</span>);
        ByteBufUtil.log(buffer);

        buffer.writeIntLE(<span class="hljs-number">6</span>);
        ByteBufUtil.log(buffer);

        buffer.writeLong(<span class="hljs-number">7</span>);
        ByteBufUtil.log(buffer);
    &#125;
&#125;</code></pre>

<p><strong>运行结果</strong></p>
<pre><code class="hljs java">read index:<span class="hljs-number">0</span> write index:<span class="hljs-number">0</span> capacity:<span class="hljs-number">16</span>

read index:<span class="hljs-number">0</span> write index:<span class="hljs-number">4</span> capacity:<span class="hljs-number">16</span>
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span>                                     |....            |
+--------+-------------------------------------------------+----------------+

read index:<span class="hljs-number">0</span> write index:<span class="hljs-number">8</span> capacity:<span class="hljs-number">16</span>
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">05</span>                         |........        |
+--------+-------------------------------------------------+----------------+

read index:<span class="hljs-number">0</span> write index:<span class="hljs-number">12</span> capacity:<span class="hljs-number">16</span>
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span>             |............    |
+--------+-------------------------------------------------+----------------+

read index:<span class="hljs-number">0</span> write index:<span class="hljs-number">20</span> capacity:<span class="hljs-number">20</span>
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> |................|
|<span class="hljs-number">00000010</span>| <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">07</span>                                     |....            |
+--------+-------------------------------------------------+----------------+</code></pre>

<p>还有一类方法是 <strong>set 开头</strong>的一系列方法，也<strong>可以写入数据，但不会改变写指针位置</strong></p>
<h3 id="扩容"><a href="#扩容" class="headerlink" title="扩容"></a>扩容</h3><p>当ByteBuf中的容量无法容纳写入的数据时，会进行扩容操作</p>
<pre><code class="hljs java">buffer.writeLong(<span class="hljs-number">7</span>);
ByteBufUtil.log(buffer);</code></pre>

<pre><code class="hljs java"><span class="hljs-comment">// 扩容前</span>
read index:<span class="hljs-number">0</span> write index:<span class="hljs-number">12</span> capacity:<span class="hljs-number">16</span>
...

<span class="hljs-comment">// 扩容后</span>
read index:<span class="hljs-number">0</span> write index:<span class="hljs-number">20</span> capacity:<span class="hljs-number">20</span>
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> |................|
|<span class="hljs-number">00000010</span>| <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">07</span>                                     |....            |
+--------+-------------------------------------------------+----------------+</code></pre>



<h4 id="扩容规则"><a href="#扩容规则" class="headerlink" title="扩容规则"></a>扩容规则</h4><ul>
<li>如何写入后数据大小未超过 512 字节，则选择下一个 16 的整数倍进行扩容<ul>
<li>例如写入后大小为 12 字节，则扩容后 capacity 是 16 字节</li>
</ul>
</li>
<li>如果写入后数据大小超过 512 字节，则选择下一个  2<sup>n</sup> <ul>
<li>例如写入后大小为 513 字节，则扩容后 capacity 是 2<sup>10</sup>=1024 字节（2<sup>9</sup>=512 已经不够了）</li>
</ul>
</li>
<li>扩容<strong>不能超过</strong> maxCapacity，否则会抛出<code>java.lang.IndexOutOfBoundsException</code>异常</li>
</ul>
<pre><code class="hljs reasonml">Exception <span class="hljs-keyword">in</span> thread <span class="hljs-string">"main"</span> java.lang.IndexOutOfBoundsException: writer<span class="hljs-constructor">Index(20)</span> + min<span class="hljs-constructor">WritableBytes(8)</span> exceeds max<span class="hljs-constructor">Capacity(20)</span>: <span class="hljs-constructor">PooledUnsafeDirectByteBuf(<span class="hljs-params">ridx</span>: 0, <span class="hljs-params">widx</span>: 20, <span class="hljs-params">cap</span>: 20<span class="hljs-operator">/</span>20)</span>
...</code></pre>



<h3 id="读取"><a href="#读取" class="headerlink" title="读取"></a>读取</h3><p>读取主要是通过一系列read方法进行读取，读取时会根据读取数据的字节数移动读指针</p>
<p>如果需要<strong>重复读取</strong>，需要调用<code>buffer.markReaderIndex()</code>对读指针进行标记，并通过<code>buffer.resetReaderIndex()</code>将读指针恢复到mark标记的位置</p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ByteBufStudy</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-comment">// 创建ByteBuf</span>
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(<span class="hljs-number">16</span>, <span class="hljs-number">20</span>);

        <span class="hljs-comment">// 向buffer中写入数据</span>
        buffer.writeBytes(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]&#123;<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>&#125;);
        buffer.writeInt(<span class="hljs-number">5</span>);

        <span class="hljs-comment">// 读取4个字节</span>
        System.out.println(buffer.readByte());
        System.out.println(buffer.readByte());
        System.out.println(buffer.readByte());
        System.out.println(buffer.readByte());
        ByteBufUtil.log(buffer);

        <span class="hljs-comment">// 通过mark与reset实现重复读取</span>
        buffer.markReaderIndex();
        System.out.println(buffer.readInt());
        ByteBufUtil.log(buffer);

        <span class="hljs-comment">// 恢复到mark标记处</span>
        buffer.resetReaderIndex();
        ByteBufUtil.log(buffer);
    &#125;
&#125;</code></pre>

<pre><code class="hljs angelscript"><span class="hljs-number">1</span>
<span class="hljs-number">2</span>
<span class="hljs-number">3</span>
<span class="hljs-number">4</span>
read index:<span class="hljs-number">4</span> write index:<span class="hljs-number">8</span> capacity:<span class="hljs-number">16</span>
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">05</span>                                     |....            |
+--------+-------------------------------------------------+----------------+
<span class="hljs-number">5</span>
read index:<span class="hljs-number">8</span> write index:<span class="hljs-number">8</span> capacity:<span class="hljs-number">16</span>

read index:<span class="hljs-number">4</span> write index:<span class="hljs-number">8</span> capacity:<span class="hljs-number">16</span>
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">05</span>                                     |....            |
+--------+-------------------------------------------------+----------------+</code></pre>

<p>还有以 get 开头的一系列方法，这些<strong>方法不会改变读指针的位置</strong></p>
<h3 id="释放"><a href="#释放" class="headerlink" title="释放"></a>释放</h3><p>由于 Netty 中有堆外内存（直接内存）的 ByteBuf 实现，<strong>堆外内存最好是手动来释放</strong>，而不是等 GC 垃圾回收。</p>
<ul>
<li>UnpooledHeapByteBuf 使用的是 JVM 内存，只需等 GC 回收内存即可</li>
<li>UnpooledDirectByteBuf 使用的就是直接内存了，需要特殊的方法来回收内存</li>
<li>PooledByteBuf 和它的子类使用了池化机制，需要更复杂的规则来回收内存</li>
</ul>
<p>Netty 这里采用了引用计数法来控制回收内存，每个 ByteBuf 都实现了 ReferenceCounted 接口</p>
<ul>
<li>每个 ByteBuf 对象的初始计数为 1</li>
<li>调用 release 方法计数减 1，如果计数为 0，ByteBuf 内存被回收</li>
<li>调用 retain 方法计数加 1，表示调用者没用完之前，其它 handler 即使调用了 release 也不会造成回收</li>
<li>当计数为 0 时，底层内存会被回收，这时即使 ByteBuf 对象还在，其各个方法均无法正常使用</li>
</ul>
<h4 id="释放规则"><a href="#释放规则" class="headerlink" title="释放规则"></a>释放规则</h4><p>因为 pipeline 的存在，一般需要将 ByteBuf 传递给下一个 ChannelHandler，如果在每个 ChannelHandler 中都去调用 release ，就失去了传递性（如果在这个 ChannelHandler 内这个 ByteBuf 已完成了它的使命，那么便无须再传递）</p>
<p><strong>基本规则是，谁是最后使用者，谁负责 release</strong></p>
<ul>
<li><p>起点，对于 NIO 实现来讲，在 io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe.read 方法中首次创建 ByteBuf 放入 pipeline（line 163 pipeline.fireChannelRead(byteBuf)）</p>
</li>
<li><p>入站 ByteBuf 处理原则</p>
<ul>
<li>对原始 ByteBuf 不做处理，调用 ctx.fireChannelRead(msg) 向后传递，这时无须 release</li>
<li><strong>将原始 ByteBuf 转换为其它类型的 Java 对象，这时 ByteBuf 就没用了，必须 release</strong></li>
<li><strong>如果不调用 ctx.fireChannelRead(msg) 向后传递，那么也必须 release</strong></li>
<li><strong>注意各种异常，如果 ByteBuf 没有成功传递到下一个 ChannelHandler，必须 release</strong></li>
<li>假设消息<strong>一直向后传</strong>，那么 TailContext 会负责释放未处理消息（原始的 ByteBuf）</li>
</ul>
</li>
<li><p>出站 ByteBuf 处理原则</p>
<ul>
<li><strong>出站消息最终都会转为 ByteBuf 输出，一直向前传，由 HeadContext flush 后 release</strong></li>
</ul>
</li>
<li><p>异常处理原则</p>
<ul>
<li><p>有时候不清楚 ByteBuf 被引用了多少次，但又必须彻底释放，可以<strong>循环调用 release 直到返回 true</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">while</span> (!buffer.release()) &#123;&#125;</code></pre>





</li>
</ul>
</li>
</ul>
<p>当ByteBuf<strong>被传到了pipeline的head与tail时</strong>，ByteBuf会被其中的方法彻底释放，但<strong>前提是ByteBuf被传递到了head与tail中</strong></p>
<p><strong>TailConext中释放ByteBuf的源码</strong></p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onUnhandledInboundMessage</span><span class="hljs-params">(Object msg)</span> </span>&#123;
    <span class="hljs-keyword">try</span> &#123;
        logger.debug(<span class="hljs-string">"Discarded inbound message &#123;&#125; that reached at the tail of the pipeline. Please check your pipeline configuration."</span>, msg);
    &#125; <span class="hljs-keyword">finally</span> &#123;
        <span class="hljs-comment">// 具体的释放方法</span>
        ReferenceCountUtil.release(msg);
    &#125;
&#125;</code></pre>

<p>判断传过来的是否为ByteBuf，是的话才需要释放</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">release</span><span class="hljs-params">(Object msg)</span> </span>&#123;
	<span class="hljs-keyword">return</span> msg <span class="hljs-keyword">instanceof</span> ReferenceCounted ? ((ReferenceCounted)msg).release() : <span class="hljs-keyword">false</span>;
&#125;</code></pre>



<h3 id="切片"><a href="#切片" class="headerlink" title="切片"></a>切片</h3><p>ByteBuf切片是【零拷贝】的体现之一，对原始 ByteBuf 进行切片成多个 ByteBuf，<strong>切片后的 ByteBuf 并没有发生内存复制，还是使用原始 ByteBuf 的内存</strong>，切片后的 ByteBuf 维护独立的 read，write 指针</p>
<p>得到分片后的buffer后，要调用其retain方法，使其内部的引用计数加一。避免原ByteBuf释放，导致切片buffer无法使用</p>
<p>修改原ByteBuf中的值，也会影响切片后得到的ByteBuf</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210423154059.png" srcset="/img/loading.gif" alt=""></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestSlice</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-comment">// 创建ByteBuf</span>
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(<span class="hljs-number">16</span>, <span class="hljs-number">20</span>);

        <span class="hljs-comment">// 向buffer中写入数据</span>
        buffer.writeBytes(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]&#123;<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">9</span>, <span class="hljs-number">10</span>&#125;);

        <span class="hljs-comment">// 将buffer分成两部分</span>
        ByteBuf slice1 = buffer.slice(<span class="hljs-number">0</span>, <span class="hljs-number">5</span>);
        ByteBuf slice2 = buffer.slice(<span class="hljs-number">5</span>, <span class="hljs-number">5</span>);

        <span class="hljs-comment">// 需要让分片的buffer引用计数加一</span>
        <span class="hljs-comment">// 避免原Buffer释放导致分片buffer无法使用</span>
        slice1.retain();
        slice2.retain();
        
        ByteBufUtil.log(slice1);
        ByteBufUtil.log(slice2);

        <span class="hljs-comment">// 更改原始buffer中的值</span>
        System.out.println(<span class="hljs-string">"===========修改原buffer中的值==========="</span>);
        buffer.setByte(<span class="hljs-number">0</span>,<span class="hljs-number">5</span>);

        System.out.println(<span class="hljs-string">"===========打印slice1==========="</span>);
        ByteBufUtil.log(slice1);
    &#125;
&#125;</code></pre>

<p>运行结果</p>
<pre><code class="hljs asciidoc">read index:0 write index:5 capacity:5
<span class="hljs-code">         +-------------------------------------------------+</span>
<span class="hljs-code">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span>
<span class="hljs-code">+--------+</span>-------------------------------------------------<span class="hljs-code">+----------------+</span>
|00000000| 01 02 03 04 05                                  |.....           |
<span class="hljs-code">+--------+</span>-------------------------------------------------<span class="hljs-code">+----------------+</span>
read index:0 write index:5 capacity:5
<span class="hljs-code">         +-------------------------------------------------+</span>
<span class="hljs-code">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span>
<span class="hljs-code">+--------+</span>-------------------------------------------------<span class="hljs-code">+----------------+</span>
|00000000| 06 07 08 09 0a                                  |.....           |
<span class="hljs-code">+--------+</span>-------------------------------------------------<span class="hljs-code">+----------------+</span>
===========修改原buffer中的值===========
===========打印slice1===========
read index:0 write index:5 capacity:5
<span class="hljs-code">         +-------------------------------------------------+</span>
<span class="hljs-code">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span>
<span class="hljs-code">+--------+</span>-------------------------------------------------<span class="hljs-code">+----------------+</span>
|00000000| 05 02 03 04 05                                  |.....           |
<span class="hljs-code">+--------+</span>-------------------------------------------------<span class="hljs-code">+----------------+</span></code></pre>



<h3 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h3><ul>
<li>池化思想 - 可以重用池中 ByteBuf 实例，更节约内存，减少内存溢出的可能</li>
<li><strong>读写指针分离</strong>，不需要像 ByteBuffer 一样切换读写模式</li>
<li>可以<strong>自动扩容</strong></li>
<li>支持链式调用，使用更流畅</li>
<li>很多地方体现零拷贝，例如<ul>
<li>slice、duplicate、CompositeByteBuf</li>
</ul>
</li>
</ul>
<h1 id="四、应用"><a href="#四、应用" class="headerlink" title="四、应用"></a>四、应用</h1><h2 id="1、粘包与半包"><a href="#1、粘包与半包" class="headerlink" title="1、粘包与半包"></a>1、粘包与半包</h2><h3 id="服务器代码-1"><a href="#服务器代码-1" class="headerlink" title="服务器代码"></a>服务器代码</h3><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StudyServer</span> </span>&#123;
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Logger log = LoggerFactory.getLogger(StudyServer<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">start</span><span class="hljs-params">()</span> </span>&#123;
        NioEventLoopGroup boss = <span class="hljs-keyword">new</span> NioEventLoopGroup(<span class="hljs-number">1</span>);
        NioEventLoopGroup worker = <span class="hljs-keyword">new</span> NioEventLoopGroup();
        <span class="hljs-keyword">try</span> &#123;
            ServerBootstrap serverBootstrap = <span class="hljs-keyword">new</span> ServerBootstrap();
            serverBootstrap.channel(NioServerSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
            serverBootstrap.group(boss, worker);
            serverBootstrap.childHandler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123;
                <span class="hljs-meta">@Override</span>
                <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> </span>&#123;
                    ch.pipeline().addLast(<span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG));
                    ch.pipeline().addLast(<span class="hljs-keyword">new</span> ChannelInboundHandlerAdapter() &#123;
                        <span class="hljs-meta">@Override</span>
                        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelActive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                            <span class="hljs-comment">// 连接建立时会执行该方法</span>
                            log.debug(<span class="hljs-string">"connected &#123;&#125;"</span>, ctx.channel());
                            <span class="hljs-keyword">super</span>.channelActive(ctx);
                        &#125;

                        <span class="hljs-meta">@Override</span>
                        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelInactive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                            <span class="hljs-comment">// 连接断开时会执行该方法</span>
                            log.debug(<span class="hljs-string">"disconnect &#123;&#125;"</span>, ctx.channel());
                            <span class="hljs-keyword">super</span>.channelInactive(ctx);
                        &#125;
                    &#125;);
                &#125;
            &#125;);
            ChannelFuture channelFuture = serverBootstrap.bind(<span class="hljs-number">8080</span>);
            log.debug(<span class="hljs-string">"&#123;&#125; binding..."</span>, channelFuture.channel());
            channelFuture.sync();
            log.debug(<span class="hljs-string">"&#123;&#125; bound..."</span>, channelFuture.channel());
            <span class="hljs-comment">// 关闭channel</span>
            channelFuture.channel().closeFuture().sync();
        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;
            log.error(<span class="hljs-string">"server error"</span>, e);
        &#125; <span class="hljs-keyword">finally</span> &#123;
            boss.shutdownGracefully();
            worker.shutdownGracefully();
            log.debug(<span class="hljs-string">"stopped"</span>);
        &#125;
    &#125;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-keyword">new</span> StudyServer().start();
    &#125;
&#125;</code></pre>



<h3 id="粘包现象"><a href="#粘包现象" class="headerlink" title="粘包现象"></a>粘包现象</h3><p><strong>客户端代码</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StudyClient</span> </span>&#123;
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Logger log = LoggerFactory.getLogger(StudyClient<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        NioEventLoopGroup worker = <span class="hljs-keyword">new</span> NioEventLoopGroup();
        <span class="hljs-keyword">try</span> &#123;
            Bootstrap bootstrap = <span class="hljs-keyword">new</span> Bootstrap();
            bootstrap.channel(NioSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
            bootstrap.group(worker);
            bootstrap.handler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123;
                <span class="hljs-meta">@Override</span>
                <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                    log.debug(<span class="hljs-string">"connected..."</span>);
                    ch.pipeline().addLast(<span class="hljs-keyword">new</span> ChannelInboundHandlerAdapter() &#123;
                        <span class="hljs-meta">@Override</span>
                        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelActive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                            log.debug(<span class="hljs-string">"sending..."</span>);
                            <span class="hljs-comment">// 每次发送16个字节的数据，共发送10次</span>
                            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;
                                ByteBuf buffer = ctx.alloc().buffer();
                                buffer.writeBytes(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]&#123;<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">9</span>, <span class="hljs-number">10</span>, <span class="hljs-number">11</span>, <span class="hljs-number">12</span>, <span class="hljs-number">13</span>, <span class="hljs-number">14</span>, <span class="hljs-number">15</span>&#125;);
                                ctx.writeAndFlush(buffer);
                            &#125;
                        &#125;
                    &#125;);
                &#125;
            &#125;);
            ChannelFuture channelFuture = bootstrap.connect(<span class="hljs-string">"127.0.0.1"</span>, <span class="hljs-number">8080</span>).sync();
            channelFuture.channel().closeFuture().sync();

        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;
            log.error(<span class="hljs-string">"client error"</span>, e);
        &#125; <span class="hljs-keyword">finally</span> &#123;
            worker.shutdownGracefully();
        &#125;
    &#125;
&#125;</code></pre>



<p><strong>服务器接收结果</strong></p>
<pre><code class="hljs angelscript"><span class="hljs-number">7999</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x5b43ecb0</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">53797</span>] READ: <span class="hljs-number">160</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
|<span class="hljs-number">00000010</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
|<span class="hljs-number">00000020</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
|<span class="hljs-number">00000030</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
|<span class="hljs-number">00000040</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
|<span class="hljs-number">00000050</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
|<span class="hljs-number">00000060</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
|<span class="hljs-number">00000070</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
|<span class="hljs-number">00000080</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
|<span class="hljs-number">00000090</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
+--------+-------------------------------------------------+----------------+</code></pre>

<p>可见虽然客户端是分别以16字节为单位，通过channel向服务器发送了10次数据，可是<strong>服务器端却只接收了一次，接收数据的大小为160B，即客户端发送的数据总大小，这就是粘包现象</strong></p>
<h3 id="半包现象"><a href="#半包现象" class="headerlink" title="半包现象"></a>半包现象</h3><p>将客户端-服务器之间的channel容量进行调整</p>
<p><strong>服务器代码</strong></p>
<pre><code class="hljs java"><span class="hljs-comment">// 调整channel的容量</span>
serverBootstrap.option(ChannelOption.SO_RCVBUF, <span class="hljs-number">10</span>);</code></pre>

<p><strong>注意</strong></p>
<blockquote>
<p>serverBootstrap.option(ChannelOption.SO_RCVBUF, 10) 影响的底层接收缓冲区（即滑动窗口）大小，仅决定了 netty 读取的最小单位，<strong>netty 实际每次读取的一般是它的整数倍</strong></p>
</blockquote>
<p><strong>服务器接收结果</strong></p>
<pre><code class="hljs angelscript"><span class="hljs-number">5901</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0xc73284f3</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">49679</span>] READ: <span class="hljs-number">36</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
|<span class="hljs-number">00000010</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
|<span class="hljs-number">00000020</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span>                                     |....            |
+--------+-------------------------------------------------+----------------+

<span class="hljs-number">5901</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0xc73284f3</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">49679</span>] READ: <span class="hljs-number">40</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> |................|
|<span class="hljs-number">00000010</span>| <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> |................|
|<span class="hljs-number">00000020</span>| <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b                         |........        |
+--------+-------------------------------------------------+----------------+

<span class="hljs-number">5901</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0xc73284f3</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">49679</span>] READ: <span class="hljs-number">40</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b |................|
|<span class="hljs-number">00000010</span>| <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b |................|
|<span class="hljs-number">00000020</span>| <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span>                         |........        |
+--------+-------------------------------------------------+----------------+

<span class="hljs-number">5901</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0xc73284f3</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">49679</span>] READ: <span class="hljs-number">40</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> |................|
|<span class="hljs-number">00000010</span>| <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> |................|
|<span class="hljs-number">00000020</span>| <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b                         |........        |
+--------+-------------------------------------------------+----------------+

<span class="hljs-number">5901</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0xc73284f3</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">49679</span>] READ: <span class="hljs-number">4</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span>                                     |....            |
+--------+-------------------------------------------------+----------------+</code></pre>

<p>可见客户端每次发送的数据，<strong>因channel容量不足，无法将发送的数据一次性接收</strong>，便产生了半包现象</p>
<h3 id="现象分析"><a href="#现象分析" class="headerlink" title="现象分析"></a>现象分析</h3><h4 id="粘包"><a href="#粘包" class="headerlink" title="粘包"></a>粘包</h4><ul>
<li>现象<ul>
<li>发送 abc def，接收 abcdef</li>
</ul>
</li>
<li>原因<ul>
<li>应用层<ul>
<li>接收方 ByteBuf 设置太大（Netty 默认 1024）</li>
</ul>
</li>
<li>传输层-网络层<ul>
<li>滑动窗口：假设发送方 256 bytes 表示一个完整报文，但由于接收方处理不及时且<strong>窗口大小足够大（大于256 bytes），这 256 bytes 字节就会缓冲在接收方的滑动窗口中，</strong>当滑动窗口中缓冲了多个报文就会粘包</li>
<li>Nagle 算法：会造成粘包</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="半包"><a href="#半包" class="headerlink" title="半包"></a>半包</h4><ul>
<li>现象<ul>
<li>发送 abcdef，接收 abc def</li>
</ul>
</li>
<li>原因<ul>
<li>应用层<ul>
<li>接收方 ByteBuf 小于实际发送数据量</li>
</ul>
</li>
<li>传输层-网络层<ul>
<li>滑动窗口：假设接收方的窗口只剩了 128 bytes，发送方的报文大小是 256 bytes，这时<strong>接收方窗口中无法容纳发送方的全部报文，发送方只能先发送前 128 bytes，等待 ack 后才能发送剩余部分，这就造成了半包</strong></li>
</ul>
</li>
<li>数据链路层<ul>
<li>MSS 限制：当发送的数据超过 MSS 限制后，会将数据切分发送，就会造成半包</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="本质"><a href="#本质" class="headerlink" title="本质"></a>本质</h4><p>发生粘包与半包现象的本质是<strong>因为 TCP 是流式协议，消息无边界</strong></p>
<h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><h4 id="短链接"><a href="#短链接" class="headerlink" title="短链接"></a>短链接</h4><p><strong>客户端每次向服务器发送数据以后，就与服务器断开连接，此时的消息边界为连接建立到连接断开</strong>。这时便无需使用滑动窗口等技术来缓冲数据，则不会发生粘包现象。但如果一次性数据发送过多，接收方无法一次性容纳所有数据，还是会发生半包现象，所以<strong>短链接无法解决半包现象</strong></p>
<p><strong>客户端代码改进</strong></p>
<p>修改channelActive方法</p>
<pre><code class="hljs JAVA"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelActive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
    log.debug(<span class="hljs-string">"sending..."</span>);
    ByteBuf buffer = ctx.alloc().buffer(<span class="hljs-number">16</span>);
    buffer.writeBytes(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]&#123;<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">9</span>, <span class="hljs-number">10</span>, <span class="hljs-number">11</span>, <span class="hljs-number">12</span>, <span class="hljs-number">13</span>, <span class="hljs-number">14</span>, <span class="hljs-number">15</span>&#125;);
    ctx.writeAndFlush(buffer);
    <span class="hljs-comment">// 使用短链接，每次发送完毕后就断开连接</span>
    ctx.channel().close();
&#125;</code></pre>

<p>将发送步骤整体封装为send()方法，调用10次send()方法，模拟发送10次数据</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
    <span class="hljs-comment">// 发送10次</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;
        send();
    &#125;
&#125;</code></pre>



<p><strong>运行结果</strong></p>
<pre><code class="hljs angelscript"><span class="hljs-number">6452</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x3eb6a684</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">65024</span>] ACTIVE

<span class="hljs-number">6468</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x3eb6a684</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">65024</span>] READ: <span class="hljs-number">16</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
+--------+-------------------------------------------------+----------------+

<span class="hljs-number">6468</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x3eb6a684</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> ! R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">65024</span>] INACTIVE

<span class="hljs-number">6483</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-2</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x7dcc31ff</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">65057</span>] ACTIVE

<span class="hljs-number">6483</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-2</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x7dcc31ff</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">65057</span>] READ: <span class="hljs-number">16</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">02</span> <span class="hljs-number">03</span> <span class="hljs-number">04</span> <span class="hljs-number">05</span> <span class="hljs-number">06</span> <span class="hljs-number">07</span> <span class="hljs-number">08</span> <span class="hljs-number">09</span> <span class="hljs-number">0</span>a <span class="hljs-number">0</span>b <span class="hljs-number">0</span>c <span class="hljs-number">0</span>d <span class="hljs-number">0</span>e <span class="hljs-number">0f</span> |................|
+--------+-------------------------------------------------+----------------+

<span class="hljs-number">6483</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-2</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x7dcc31ff</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> ! R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">65057</span>] INACTIVE

...</code></pre>

<p>客户端先于服务器建立连接，此时控制台打印<code>ACTIVE</code>，之后客户端向服务器发送了16B的数据，发送后断开连接，此时控制台打印<code>INACTIVE</code>，可见<strong>未出现粘包现象</strong></p>
<h4 id="定长解码器"><a href="#定长解码器" class="headerlink" title="定长解码器"></a>定长解码器</h4><p>客户端于服务器<strong>约定一个最大长度，保证客户端每次发送的数据长度都不会大于该长度</strong>。若发送数据长度不足则需要<strong>补齐</strong>至该长度</p>
<p>服务器接收数据时，<strong>将接收到的数据按照约定的最大长度进行拆分</strong>，即使发送过程中产生了粘包，也可以通过定长解码器将数据正确地进行拆分。<strong>服务端需要用到<code>FixedLengthFrameDecoder</code>对数据进行定长解码</strong>，具体使用方法如下</p>
<pre><code class="hljs java">ch.pipeline().addLast(<span class="hljs-keyword">new</span> FixedLengthFrameDecoder(<span class="hljs-number">16</span>));</code></pre>



<p><strong>客户端代码</strong></p>
<p>客户端发送数据的代码如下</p>
<pre><code class="hljs java"><span class="hljs-comment">// 约定最大长度为16</span>
<span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> maxLength = <span class="hljs-number">16</span>;
<span class="hljs-comment">// 被发送的数据</span>
<span class="hljs-keyword">char</span> c = <span class="hljs-string">'a'</span>;
<span class="hljs-comment">// 向服务器发送10个报文</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;
    ByteBuf buffer = ctx.alloc().buffer(maxLength);
    <span class="hljs-comment">// 定长byte数组，未使用部分会以0进行填充</span>
    <span class="hljs-keyword">byte</span>[] bytes = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[maxLength];
    <span class="hljs-comment">// 生成长度为0~15的数据</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j &lt; (<span class="hljs-keyword">int</span>)(Math.random()*(maxLength-<span class="hljs-number">1</span>)); j++) &#123;
        bytes[j] = (<span class="hljs-keyword">byte</span>) c;
    &#125;
    buffer.writeBytes(bytes);
    c++;
    <span class="hljs-comment">// 将数据发送给服务器</span>
    ctx.writeAndFlush(buffer);
&#125;</code></pre>

<p><strong>服务器代码</strong></p>
<p>使用<code>FixedLengthFrameDecoder</code>对粘包数据进行拆分，该handler需要添加在<code>LoggingHandler</code>之前，保证数据被打印时已被拆分</p>
<pre><code class="hljs java"><span class="hljs-comment">// 通过定长解码器对粘包数据进行拆分</span>
ch.pipeline().addLast(<span class="hljs-keyword">new</span> FixedLengthFrameDecoder(<span class="hljs-number">16</span>));
ch.pipeline().addLast(<span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG));</code></pre>

<p><strong>运行结果</strong></p>
<pre><code class="hljs angelscript"><span class="hljs-number">8222</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0xbc122d07</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">52954</span>] READ: <span class="hljs-number">16</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> |aaaa............|
+--------+-------------------------------------------------+----------------+

<span class="hljs-number">8222</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0xbc122d07</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">52954</span>] READ: <span class="hljs-number">16</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">62</span> <span class="hljs-number">62</span> <span class="hljs-number">62</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> |bbb.............|
+--------+-------------------------------------------------+----------------+


<span class="hljs-number">8222</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0xbc122d07</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">52954</span>] READ: <span class="hljs-number">16</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">63</span> <span class="hljs-number">63</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> |cc..............|
+--------+-------------------------------------------------+----------------+

...</code></pre>



<h4 id="行解码器"><a href="#行解码器" class="headerlink" title="行解码器"></a>行解码器</h4><p>行解码器的是<strong>通过分隔符对数据进行拆分</strong>来解决粘包半包问题的</p>
<p>可以通过<code>LineBasedFrameDecoder(int maxLength)</code>来拆分以<strong>换行符(\n)</strong>为分隔符的数据，也可以通过<code>DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters)</code>来<strong>指定通过什么分隔符来拆分数据（可以传入多个分隔符）</strong></p>
<p>两种解码器<strong>都需要传入数据的最大长度</strong>，若超出最大长度，会抛出<code>TooLongFrameException</code>异常</p>
<p><strong>以换行符 \n 为分隔符</strong></p>
<p>客户端代码</p>
<pre><code class="hljs java"><span class="hljs-comment">// 约定最大长度为 64</span>
<span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> maxLength = <span class="hljs-number">64</span>;
<span class="hljs-comment">// 被发送的数据</span>
<span class="hljs-keyword">char</span> c = <span class="hljs-string">'a'</span>;
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) &#123;
    ByteBuf buffer = ctx.alloc().buffer(maxLength);
    <span class="hljs-comment">// 生成长度为0~62的数据</span>
    Random random = <span class="hljs-keyword">new</span> Random();
    StringBuilder sb = <span class="hljs-keyword">new</span> StringBuilder();
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j &lt; (<span class="hljs-keyword">int</span>)(random.nextInt(maxLength-<span class="hljs-number">2</span>)); j++) &#123;
        sb.append(c);
    &#125;
    <span class="hljs-comment">// 数据以 \n 结尾</span>
    sb.append(<span class="hljs-string">"\n"</span>);
    buffer.writeBytes(sb.toString().getBytes(StandardCharsets.UTF_8));
    c++;
    <span class="hljs-comment">// 将数据发送给服务器</span>
    ctx.writeAndFlush(buffer);
&#125;</code></pre>

<p>服务器代码</p>
<pre><code class="hljs java"><span class="hljs-comment">// 通过行解码器对粘包数据进行拆分，以 \n 为分隔符</span>
<span class="hljs-comment">// 需要指定最大长度</span>
ch.pipeline().addLast(<span class="hljs-keyword">new</span> DelimiterBasedFrameDecoder(<span class="hljs-number">64</span>));
ch.pipeline().addLast(<span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG));</code></pre>

<p>运行结果</p>
<pre><code class="hljs angelscript"><span class="hljs-number">4184</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x9d6ac701</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">58282</span>] READ: <span class="hljs-number">10</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span>                   |aaaaaaaaaa      |
+--------+-------------------------------------------------+----------------+

<span class="hljs-number">4184</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x9d6ac701</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">58282</span>] READ: <span class="hljs-number">11</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">62</span> <span class="hljs-number">62</span> <span class="hljs-number">62</span> <span class="hljs-number">62</span> <span class="hljs-number">62</span> <span class="hljs-number">62</span> <span class="hljs-number">62</span> <span class="hljs-number">62</span> <span class="hljs-number">62</span> <span class="hljs-number">62</span> <span class="hljs-number">62</span>                |bbbbbbbbbbb     |
+--------+-------------------------------------------------+----------------+

<span class="hljs-number">4184</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x9d6ac701</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">58282</span>] READ: <span class="hljs-number">2</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">63</span> <span class="hljs-number">63</span>                                           |cc              |
+--------+-------------------------------------------------+----------------+

...</code></pre>



<p><strong>以自定义分隔符 \c 为分隔符</strong></p>
<p>客户端代码</p>
<pre><code class="hljs java">...
    
<span class="hljs-comment">// 数据以 \c 结尾</span>
sb.append(<span class="hljs-string">"\\c"</span>);
buffer.writeBytes(sb.toString().getBytes(StandardCharsets.UTF_8));

...</code></pre>

<p>服务器代码</p>
<pre><code class="hljs java"><span class="hljs-comment">// 将分隔符放入ByteBuf中</span>
ByteBuf bufSet = ch.alloc().buffer().writeBytes(<span class="hljs-string">"\\c"</span>.getBytes(StandardCharsets.UTF_8));
<span class="hljs-comment">// 通过行解码器对粘包数据进行拆分，以 \c 为分隔符</span>
ch.pipeline().addLast(<span class="hljs-keyword">new</span> DelimiterBasedFrameDecoder(<span class="hljs-number">64</span>, ch.alloc().buffer().writeBytes(bufSet)));
ch.pipeline().addLast(<span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG));</code></pre>

<p>运行结果</p>
<pre><code class="hljs angelscript"><span class="hljs-number">8246</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x86215ccd</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">65159</span>] READ: <span class="hljs-number">14</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span> <span class="hljs-number">61</span>       |aaaaaaaaaaaaaa  |
+--------+-------------------------------------------------+----------------+


<span class="hljs-number">8247</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x86215ccd</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">65159</span>] READ: <span class="hljs-number">3</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">62</span> <span class="hljs-number">62</span> <span class="hljs-number">62</span>                                        |bbb             |
+--------+-------------------------------------------------+----------------+

...</code></pre>



<h4 id="长度字段解码器"><a href="#长度字段解码器" class="headerlink" title="长度字段解码器"></a>长度字段解码器</h4><p>在传送数据时可以在数据中<strong>添加一个用于表示有用数据长度的字段</strong>，在解码时读取出这个用于表明长度的字段，同时读取其他相关参数，即可知道最终需要的数据是什么样子的</p>
<p><code>LengthFieldBasedFrameDecoder</code>解码器可以提供更为丰富的拆分方法，其构造方法有五个参数</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">LengthFieldBasedFrameDecoder</span><span class="hljs-params">(</span></span>
<span class="hljs-function"><span class="hljs-params">    <span class="hljs-keyword">int</span> maxFrameLength,</span></span>
<span class="hljs-function"><span class="hljs-params">    <span class="hljs-keyword">int</span> lengthFieldOffset, <span class="hljs-keyword">int</span> lengthFieldLength,</span></span>
<span class="hljs-function"><span class="hljs-params">    <span class="hljs-keyword">int</span> lengthAdjustment, <span class="hljs-keyword">int</span> initialBytesToStrip)</span></span></code></pre>

<p><strong>参数解析</strong></p>
<ul>
<li><p>maxFrameLength 数据最大长度</p>
<ul>
<li>表示数据的最大长度（包括附加信息、长度标识等内容）</li>
</ul>
</li>
<li><p>lengthFieldOffset  <strong>数据长度标识的起始偏移量</strong></p>
<ul>
<li>用于指明数据第几个字节开始是用于标识有用字节长度的，因为前面可能还有其他附加信息</li>
</ul>
</li>
<li><p>lengthFieldLength <strong>数据长度标识所占字节数</strong>（用于指明有用数据的长度）</p>
<ul>
<li>数据中用于表示有用数据长度的标识所占的字节数</li>
</ul>
</li>
<li><p>lengthAdjustment <strong>长度表示与有用数据的偏移量</strong></p>
<ul>
<li>用于指明数据长度标识和有用数据之间的距离，因为两者之间还可能有附加信息</li>
</ul>
</li>
<li><p>initialBytesToStrip <strong>数据读取起点</strong></p>
<ul>
<li>读取起点，<strong>不读取</strong> 0 ~ initialBytesToStrip 之间的数据</li>
</ul>
</li>
</ul>
<p><strong>参数图解</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210425200007.png" srcset="/img/loading.gif" alt=""></p>
<pre><code class="hljs sql">lengthFieldOffset   = 0
lengthFieldLength   = 2
lengthAdjustment    = 0
initialBytesToStrip = 0 (= <span class="hljs-keyword">do</span> <span class="hljs-keyword">not</span> strip header)
  
<span class="hljs-keyword">BEFORE</span> <span class="hljs-keyword">DECODE</span> (<span class="hljs-number">14</span> <span class="hljs-keyword">bytes</span>)         <span class="hljs-keyword">AFTER</span> <span class="hljs-keyword">DECODE</span> (<span class="hljs-number">14</span> <span class="hljs-keyword">bytes</span>)
+<span class="hljs-comment">--------+----------------+      +--------+----------------+</span>
| <span class="hljs-keyword">Length</span> | Actual <span class="hljs-keyword">Content</span> |<span class="hljs-comment">-----&gt;| Length | Actual Content |</span>
| <span class="hljs-number">0x000C</span> | <span class="hljs-string">"HELLO, WORLD"</span> |      | <span class="hljs-number">0x000C</span> | <span class="hljs-string">"HELLO, WORLD"</span> |
+<span class="hljs-comment">--------+----------------+      +--------+----------------+</span></code></pre>

<p>从0开始即为长度标识，长度标识长度为2个字节</p>
<p><strong>0x000C</strong> 即为后面 <code>HELLO, WORLD</code>的长度</p>
<hr>
<pre><code class="hljs gherkin">lengthFieldOffset   = 0
lengthFieldLength   = 2
lengthAdjustment    = 0
initialBytesToStrip = 2 (= the length of the Length field)
  
BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
+--------+----------------+      +----------------+
|<span class="hljs-string"> Length </span>|<span class="hljs-string"> Actual Content </span>|<span class="hljs-string">-----&gt;</span>|<span class="hljs-string"> Actual Content </span>|
|<span class="hljs-string"> 0x000C </span>|<span class="hljs-string"> "HELLO, WORLD" </span>|<span class="hljs-string">      </span>|<span class="hljs-string"> "HELLO, WORLD" </span>|
+--------+----------------+      +----------------+</code></pre>

<p>从0开始即为长度标识，长度标识长度为2个字节，<strong>读取时从第二个字节开始读取</strong>（此处即跳过长度标识）</p>
<p>因为<strong>跳过了用于表示长度的2个字节</strong>，所以此处直接读取<code>HELLO, WORLD</code></p>
<hr>
<pre><code class="hljs gherkin">lengthFieldOffset   = 2 (= the length of Header 1)
lengthFieldLength   = 3
lengthAdjustment    = 0
initialBytesToStrip = 0
  
BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
+----------+----------+----------------+      +----------+----------+----------------+
|<span class="hljs-string"> Header 1 </span>|<span class="hljs-string">  Length  </span>|<span class="hljs-string"> Actual Content </span>|<span class="hljs-string">-----&gt;</span>|<span class="hljs-string"> Header 1 </span>|<span class="hljs-string">  Length  </span>|<span class="hljs-string"> Actual Content </span>|
|<span class="hljs-string">  0xCAFE  </span>|<span class="hljs-string"> 0x00000C </span>|<span class="hljs-string"> "HELLO, WORLD" </span>|<span class="hljs-string">      </span>|<span class="hljs-string">  0xCAFE  </span>|<span class="hljs-string"> 0x00000C </span>|<span class="hljs-string"> "HELLO, WORLD" </span>|
+----------+----------+----------------+      +----------+----------+----------------+</code></pre>

<p>长度标识<strong>前面还有2个字节的其他内容</strong>（0xCAFE），第三个字节开始才是长度标识，长度表示长度为3个字节(0x00000C)</p>
<p>Header1中有附加信息，<strong>读取长度标识时需要跳过这些附加信息来获取长度</strong></p>
<hr>
<pre><code class="hljs gherkin">lengthFieldOffset   = 0
lengthFieldLength   = 3
lengthAdjustment    = 2 (= the length of Header 1)
initialBytesToStrip = 0
  
BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
+----------+----------+----------------+      +----------+----------+----------------+
|<span class="hljs-string">  Length  </span>|<span class="hljs-string"> Header 1 </span>|<span class="hljs-string"> Actual Content </span>|<span class="hljs-string">-----&gt;</span>|<span class="hljs-string">  Length  </span>|<span class="hljs-string"> Header 1 </span>|<span class="hljs-string"> Actual Content </span>|
|<span class="hljs-string"> 0x00000C </span>|<span class="hljs-string">  0xCAFE  </span>|<span class="hljs-string"> "HELLO, WORLD" </span>|<span class="hljs-string">      </span>|<span class="hljs-string"> 0x00000C </span>|<span class="hljs-string">  0xCAFE  </span>|<span class="hljs-string"> "HELLO, WORLD" </span>|
+----------+----------+----------------+      +----------+----------+----------------+</code></pre>

<p>从0开始即为长度标识，长度标识长度为3个字节，<strong>长度标识之后还有2个字节的其他内容</strong>（0xCAFE）</p>
<p>长度标识(0x00000C)表示的是<strong>从其后lengthAdjustment（2个字节）开始的数据的长度，即<code>HELLO, WORLD</code></strong>，不包括0xCAFE</p>
<hr>
<pre><code class="hljs gherkin">lengthFieldOffset   = 1 (= the length of HDR1)
lengthFieldLength   = 2
lengthAdjustment    = 1 (= the length of HDR2)
initialBytesToStrip = 3 (= the length of HDR1 + LEN)
  
BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
+------+--------+------+----------------+      +------+----------------+
|<span class="hljs-string"> HDR1 </span>|<span class="hljs-string"> Length </span>|<span class="hljs-string"> HDR2 </span>|<span class="hljs-string"> Actual Content </span>|<span class="hljs-string">-----&gt;</span>|<span class="hljs-string"> HDR2 </span>|<span class="hljs-string"> Actual Content </span>|
|<span class="hljs-string"> 0xCA </span>|<span class="hljs-string"> 0x000C </span>|<span class="hljs-string"> 0xFE </span>|<span class="hljs-string"> "HELLO, WORLD" </span>|<span class="hljs-string">      </span>|<span class="hljs-string"> 0xFE </span>|<span class="hljs-string"> "HELLO, WORLD" </span>|
+------+--------+------+----------------+      +------+----------------+</code></pre>

<p>长度标识<strong>前面有1个字节的其他内容，后面也有1个字节的其他内容，读取时从长度标识之后3个字节处开始读取</strong>，即读取 <code>0xFE HELLO, WORLD</code></p>
<hr>
<p><strong>使用</strong></p>
<p>通过 <strong>EmbeddedChannel</strong> 对 handler 进行测试</p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EncoderStudy</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-comment">// 模拟服务器</span>
        <span class="hljs-comment">// 使用EmbeddedChannel测试handler</span>
        EmbeddedChannel channel = <span class="hljs-keyword">new</span> EmbeddedChannel(
                <span class="hljs-comment">// 数据最大长度为1KB，长度标识前后各有1个字节的附加信息，长度标识长度为4个字节（int）</span>
                <span class="hljs-keyword">new</span> LengthFieldBasedFrameDecoder(<span class="hljs-number">1024</span>, <span class="hljs-number">1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>),
                <span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG)
        );

        <span class="hljs-comment">// 模拟客户端，写入数据</span>
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        send(buffer, <span class="hljs-string">"Hello"</span>);
        channel.writeInbound(buffer);
        send(buffer, <span class="hljs-string">"World"</span>);
        channel.writeInbound(buffer);
    &#125;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">send</span><span class="hljs-params">(ByteBuf buf, String msg)</span> </span>&#123;
        <span class="hljs-comment">// 得到数据的长度</span>
        <span class="hljs-keyword">int</span> length = msg.length();
        <span class="hljs-keyword">byte</span>[] bytes = msg.getBytes(StandardCharsets.UTF_8);
        <span class="hljs-comment">// 将数据信息写入buf</span>
        <span class="hljs-comment">// 写入长度标识前的其他信息</span>
        buf.writeByte(<span class="hljs-number">0xCA</span>);
        <span class="hljs-comment">// 写入数据长度标识</span>
        buf.writeInt(length);
        <span class="hljs-comment">// 写入长度标识后的其他信息</span>
        buf.writeByte(<span class="hljs-number">0xFE</span>);
        <span class="hljs-comment">// 写入具体的数据</span>
        buf.writeBytes(bytes);
    &#125;
&#125;</code></pre>

<p>运行结果</p>
<pre><code class="hljs angelscript"><span class="hljs-number">146</span>  [main] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0xe</span>mbedded, L:embedded - R:embedded] READ: <span class="hljs-number">11</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| ca <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">05</span> fe <span class="hljs-number">48</span> <span class="hljs-number">65</span> <span class="hljs-number">6</span>c <span class="hljs-number">6</span>c <span class="hljs-number">6f</span>                |......Hello     |
+--------+-------------------------------------------------+----------------+

<span class="hljs-number">146</span>  [main] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0xe</span>mbedded, L:embedded - R:embedded] READ: <span class="hljs-number">11</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| ca <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">05</span> fe <span class="hljs-number">57</span> <span class="hljs-number">6f</span> <span class="hljs-number">72</span> <span class="hljs-number">6</span>c <span class="hljs-number">64</span>                |......World     |
+--------+-------------------------------------------------+----------------+</code></pre>



<h2 id="2、协议设计与解析"><a href="#2、协议设计与解析" class="headerlink" title="2、协议设计与解析"></a>2、协议设计与解析</h2><h3 id="协议的作用"><a href="#协议的作用" class="headerlink" title="协议的作用"></a>协议的作用</h3><p>TCP/IP 中消息传输基于流的方式，没有边界</p>
<p><strong>协议的目的就是划定消息的边界，制定通信双方要共同遵守的通信规则</strong></p>
<h3 id="Redis协议"><a href="#Redis协议" class="headerlink" title="Redis协议"></a>Redis协议</h3><p>如果我们要向Redis服务器发送一条<code>set name Nyima</code>的指令，需要遵守如下协议</p>
<pre><code class="hljs taggerscript">// 该指令一共有3部分，每条指令之后都要添加回车与换行符
*3<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span>
// 第一个指令的长度是3
$3<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span>
// 第一个指令是set指令
set<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span>
// 下面的指令以此类推
$4<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span>
name<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span>
$5<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span>
Nyima<span class="hljs-symbol">\r</span><span class="hljs-symbol">\n</span></code></pre>



<p><strong>客户端代码如下</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RedisClient</span> </span>&#123;
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Logger log = LoggerFactory.getLogger(StudyServer<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        NioEventLoopGroup group =  <span class="hljs-keyword">new</span> NioEventLoopGroup();
        <span class="hljs-keyword">try</span> &#123;
            ChannelFuture channelFuture = <span class="hljs-keyword">new</span> Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>
<span class="hljs-class">                    .<span class="hljs-title">handler</span>(<span class="hljs-title">new</span> <span class="hljs-title">ChannelInitializer</span>&lt;<span class="hljs-title">SocketChannel</span>&gt;() </span>&#123;
                        <span class="hljs-meta">@Override</span>
                        <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> </span>&#123;
                            <span class="hljs-comment">// 打印日志</span>
                            ch.pipeline().addLast(<span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG));
                            ch.pipeline().addLast(<span class="hljs-keyword">new</span> ChannelInboundHandlerAdapter() &#123;
                                <span class="hljs-meta">@Override</span>
                                <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelActive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                                    <span class="hljs-comment">// 回车与换行符</span>
                                    <span class="hljs-keyword">final</span> <span class="hljs-keyword">byte</span>[] LINE = &#123;<span class="hljs-string">'\r'</span>,<span class="hljs-string">'\n'</span>&#125;;
                                    <span class="hljs-comment">// 获得ByteBuf</span>
                                    ByteBuf buffer = ctx.alloc().buffer();
                                    <span class="hljs-comment">// 连接建立后，向Redis中发送一条指令，注意添加回车与换行</span>
                                    <span class="hljs-comment">// set name Nyima</span>
                                    buffer.writeBytes(<span class="hljs-string">"*3"</span>.getBytes());
                                    buffer.writeBytes(LINE);
                                    buffer.writeBytes(<span class="hljs-string">"$3"</span>.getBytes());
                                    buffer.writeBytes(LINE);
                                    buffer.writeBytes(<span class="hljs-string">"set"</span>.getBytes());
                                    buffer.writeBytes(LINE);
                                    buffer.writeBytes(<span class="hljs-string">"$4"</span>.getBytes());
                                    buffer.writeBytes(LINE);
                                    buffer.writeBytes(<span class="hljs-string">"name"</span>.getBytes());
                                    buffer.writeBytes(LINE);
                                    buffer.writeBytes(<span class="hljs-string">"$5"</span>.getBytes());
                                    buffer.writeBytes(LINE);
                                    buffer.writeBytes(<span class="hljs-string">"Nyima"</span>.getBytes());
                                    buffer.writeBytes(LINE);
                                    ctx.writeAndFlush(buffer);
                                &#125;

                            &#125;);
                        &#125;
                    &#125;)
                    .connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">"localhost"</span>, <span class="hljs-number">6379</span>));
            channelFuture.sync();
            <span class="hljs-comment">// 关闭channel</span>
            channelFuture.channel().close().sync();
        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;
            e.printStackTrace();
        &#125; <span class="hljs-keyword">finally</span> &#123;
            <span class="hljs-comment">// 关闭group</span>
            group.shutdownGracefully();
        &#125;
    &#125;
&#125;</code></pre>

<p><strong>控制台打印结果</strong></p>
<pre><code class="hljs angelscript"><span class="hljs-number">1600</span> [nioEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x28c994f1</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">60792</span> - R:localhost/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">6379</span>] WRITE: <span class="hljs-number">34</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">2</span>a <span class="hljs-number">33</span> <span class="hljs-number">0</span>d <span class="hljs-number">0</span>a <span class="hljs-number">24</span> <span class="hljs-number">33</span> <span class="hljs-number">0</span>d <span class="hljs-number">0</span>a <span class="hljs-number">73</span> <span class="hljs-number">65</span> <span class="hljs-number">74</span> <span class="hljs-number">0</span>d <span class="hljs-number">0</span>a <span class="hljs-number">24</span> <span class="hljs-number">34</span> <span class="hljs-number">0</span>d |*<span class="hljs-number">3.</span>.$<span class="hljs-number">3.</span>.<span class="hljs-keyword">set</span>..$<span class="hljs-number">4.</span>|
|<span class="hljs-number">00000010</span>| <span class="hljs-number">0</span>a <span class="hljs-number">6</span>e <span class="hljs-number">61</span> <span class="hljs-number">6</span>d <span class="hljs-number">65</span> <span class="hljs-number">0</span>d <span class="hljs-number">0</span>a <span class="hljs-number">24</span> <span class="hljs-number">35</span> <span class="hljs-number">0</span>d <span class="hljs-number">0</span>a <span class="hljs-number">4</span>e <span class="hljs-number">79</span> <span class="hljs-number">69</span> <span class="hljs-number">6</span>d <span class="hljs-number">61</span> |.name..$<span class="hljs-number">5.</span>.Nyima|
|<span class="hljs-number">00000020</span>| <span class="hljs-number">0</span>d <span class="hljs-number">0</span>a                                           |..              |
+--------+-------------------------------------------------+----------------+</code></pre>

<p><strong>Redis中查询执行结果</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210426101530.png" srcset="/img/loading.gif" alt=""></p>
<h3 id="HTTP协议"><a href="#HTTP协议" class="headerlink" title="HTTP协议"></a>HTTP协议</h3><p>HTTP协议在请求行请求头中都有很多的内容，自己实现较为困难，可以使用<code>HttpServerCodec</code>作为<strong>服务器端的解码器与编码器，来处理HTTP请求</strong></p>
<pre><code class="hljs java"><span class="hljs-comment">// HttpServerCodec 中既有请求的解码器 HttpRequestDecoder 又有响应的编码器 HttpResponseEncoder</span>
<span class="hljs-comment">// Codec(CodeCombine) 一般代表该类既作为 编码器 又作为 解码器</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HttpServerCodec</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">CombinedChannelDuplexHandler</span>&lt;<span class="hljs-title">HttpRequestDecoder</span>, <span class="hljs-title">HttpResponseEncoder</span>&gt;</span>
<span class="hljs-class">        <span class="hljs-keyword">implements</span> <span class="hljs-title">HttpServerUpgradeHandler</span>.<span class="hljs-title">SourceCodec</span></span></code></pre>



<p><strong>服务器代码</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HttpServer</span> </span>&#123;
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Logger log = LoggerFactory.getLogger(StudyServer<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        NioEventLoopGroup group = <span class="hljs-keyword">new</span> NioEventLoopGroup();
        <span class="hljs-keyword">new</span> ServerBootstrap()
                .group(group)
                .channel(NioServerSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>
<span class="hljs-class">                .<span class="hljs-title">childHandler</span>(<span class="hljs-title">new</span> <span class="hljs-title">ChannelInitializer</span>&lt;<span class="hljs-title">SocketChannel</span>&gt;() </span>&#123;
                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> </span>&#123;
                        ch.pipeline().addLast(<span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG));
                        <span class="hljs-comment">// 作为服务器，使用 HttpServerCodec 作为编码器与解码器</span>
                        ch.pipeline().addLast(<span class="hljs-keyword">new</span> HttpServerCodec());
                        <span class="hljs-comment">// 服务器只处理HTTPRequest</span>
                        ch.pipeline().addLast(<span class="hljs-keyword">new</span> SimpleChannelInboundHandler&lt;HttpRequest&gt;() &#123;
                            <span class="hljs-meta">@Override</span>
                            <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext ctx, HttpRequest msg)</span> </span>&#123;
                                <span class="hljs-comment">// 获得请求uri</span>
                                log.debug(msg.uri());

                                <span class="hljs-comment">// 获得完整响应，设置版本号与状态码</span>
                                DefaultFullHttpResponse response = <span class="hljs-keyword">new</span> DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK);
                                <span class="hljs-comment">// 设置响应内容</span>
                                <span class="hljs-keyword">byte</span>[] bytes = <span class="hljs-string">"&lt;h1&gt;Hello, World!&lt;/h1&gt;"</span>.getBytes(StandardCharsets.UTF_8);
                                <span class="hljs-comment">// 设置响应体长度，避免浏览器一直接收响应内容</span>
                                response.headers().setInt(CONTENT_LENGTH, bytes.length);
                                <span class="hljs-comment">// 设置响应体</span>
                                response.content().writeBytes(bytes);

                                <span class="hljs-comment">// 写回响应</span>
                                ctx.writeAndFlush(response);
                            &#125;
                        &#125;);
                    &#125;
                &#125;)
                .bind(<span class="hljs-number">8080</span>);
    &#125;
&#125;</code></pre>

<p>服务器负责处理请求并响应浏览器。所以<strong>只需要处理HTTP请求</strong>即可</p>
<pre><code class="hljs java"><span class="hljs-comment">// 服务器只处理HTTPRequest</span>
ch.pipeline().addLast(<span class="hljs-keyword">new</span> SimpleChannelInboundHandler&lt;HttpRequest&gt;()</code></pre>

<p>获得请求后，需要返回响应给浏览器。需要创建响应对象<code>DefaultFullHttpResponse</code>，设置HTTP版本号及状态码，为避免浏览器获得响应后，因为获得<code>CONTENT_LENGTH</code>而一直空转，需要添加<code>CONTENT_LENGTH</code>字段，表明响应体中数据的具体长度</p>
<pre><code class="hljs java"><span class="hljs-comment">// 获得完整响应，设置版本号与状态码</span>
DefaultFullHttpResponse response = <span class="hljs-keyword">new</span> DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK);
<span class="hljs-comment">// 设置响应内容</span>
<span class="hljs-keyword">byte</span>[] bytes = <span class="hljs-string">"&lt;h1&gt;Hello, World!&lt;/h1&gt;"</span>.getBytes(StandardCharsets.UTF_8);
<span class="hljs-comment">// 设置响应体长度，避免浏览器一直接收响应内容</span>
response.headers().setInt(CONTENT_LENGTH, bytes.length);
<span class="hljs-comment">// 设置响应体</span>
response.content().writeBytes(bytes);</code></pre>



<p><strong>运行结果</strong></p>
<p>浏览器</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210426111017.png" srcset="/img/loading.gif" alt=""></p>
<p>控制台</p>
<pre><code class="hljs angelscript"><span class="hljs-comment">// 请求内容</span>
<span class="hljs-number">1714</span> [nioEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-2</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x72630ef7</span>, L:/<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">1</span>:<span class="hljs-number">55503</span>] READ: <span class="hljs-number">688</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">47</span> <span class="hljs-number">45</span> <span class="hljs-number">54</span> <span class="hljs-number">20</span> <span class="hljs-number">2f</span> <span class="hljs-number">66</span> <span class="hljs-number">61</span> <span class="hljs-number">76</span> <span class="hljs-number">69</span> <span class="hljs-number">63</span> <span class="hljs-number">6f</span> <span class="hljs-number">6</span>e <span class="hljs-number">2</span>e <span class="hljs-number">69</span> <span class="hljs-number">63</span> <span class="hljs-number">6f</span> |GET /favicon.ico|
|<span class="hljs-number">00000010</span>| <span class="hljs-number">20</span> <span class="hljs-number">48</span> <span class="hljs-number">54</span> <span class="hljs-number">54</span> <span class="hljs-number">50</span> <span class="hljs-number">2f</span> <span class="hljs-number">31</span> <span class="hljs-number">2</span>e <span class="hljs-number">31</span> <span class="hljs-number">0</span>d <span class="hljs-number">0</span>a <span class="hljs-number">48</span> <span class="hljs-number">6f</span> <span class="hljs-number">73</span> <span class="hljs-number">74</span> <span class="hljs-number">3</span>a | HTTP/<span class="hljs-number">1.1</span>..Host:|
|<span class="hljs-number">00000020</span>| <span class="hljs-number">20</span> <span class="hljs-number">6</span>c <span class="hljs-number">6f</span> <span class="hljs-number">63</span> <span class="hljs-number">61</span> <span class="hljs-number">6</span>c <span class="hljs-number">68</span> <span class="hljs-number">6f</span> <span class="hljs-number">73</span> <span class="hljs-number">74</span> <span class="hljs-number">3</span>a <span class="hljs-number">38</span> <span class="hljs-number">30</span> <span class="hljs-number">38</span> <span class="hljs-number">30</span> <span class="hljs-number">0</span>d | localhost:<span class="hljs-number">8080.</span>|
|<span class="hljs-number">00000030</span>| <span class="hljs-number">0</span>a <span class="hljs-number">43</span> <span class="hljs-number">6f</span> <span class="hljs-number">6</span>e <span class="hljs-number">6</span>e <span class="hljs-number">65</span> <span class="hljs-number">63</span> <span class="hljs-number">74</span> <span class="hljs-number">69</span> <span class="hljs-number">6f</span> <span class="hljs-number">6</span>e <span class="hljs-number">3</span>a <span class="hljs-number">20</span> <span class="hljs-number">6</span>b <span class="hljs-number">65</span> <span class="hljs-number">65</span> |.Connection: kee|
|<span class="hljs-number">00000040</span>| <span class="hljs-number">70</span> <span class="hljs-number">2</span>d <span class="hljs-number">61</span> <span class="hljs-number">6</span>c <span class="hljs-number">69</span> <span class="hljs-number">76</span> <span class="hljs-number">65</span> <span class="hljs-number">0</span>d <span class="hljs-number">0</span>a <span class="hljs-number">50</span> <span class="hljs-number">72</span> <span class="hljs-number">61</span> <span class="hljs-number">67</span> <span class="hljs-number">6</span>d <span class="hljs-number">61</span> <span class="hljs-number">3</span>a |p-alive..Pragma:|
....

<span class="hljs-comment">// 响应内容</span>
<span class="hljs-number">1716</span> [nioEventLoopGroup<span class="hljs-number">-2</span><span class="hljs-number">-2</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x72630ef7</span>, L:/<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">0</span>:<span class="hljs-number">1</span>:<span class="hljs-number">55503</span>] WRITE: <span class="hljs-number">61</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">48</span> <span class="hljs-number">54</span> <span class="hljs-number">54</span> <span class="hljs-number">50</span> <span class="hljs-number">2f</span> <span class="hljs-number">31</span> <span class="hljs-number">2</span>e <span class="hljs-number">31</span> <span class="hljs-number">20</span> <span class="hljs-number">32</span> <span class="hljs-number">30</span> <span class="hljs-number">30</span> <span class="hljs-number">20</span> <span class="hljs-number">4f</span> <span class="hljs-number">4</span>b <span class="hljs-number">0</span>d |HTTP/<span class="hljs-number">1.1</span> <span class="hljs-number">200</span> OK.|
|<span class="hljs-number">00000010</span>| <span class="hljs-number">0</span>a <span class="hljs-number">43</span> <span class="hljs-number">6f</span> <span class="hljs-number">6</span>e <span class="hljs-number">74</span> <span class="hljs-number">65</span> <span class="hljs-number">6</span>e <span class="hljs-number">74</span> <span class="hljs-number">2</span>d <span class="hljs-number">4</span>c <span class="hljs-number">65</span> <span class="hljs-number">6</span>e <span class="hljs-number">67</span> <span class="hljs-number">74</span> <span class="hljs-number">68</span> <span class="hljs-number">3</span>a |.Content-Length:|
|<span class="hljs-number">00000020</span>| <span class="hljs-number">20</span> <span class="hljs-number">32</span> <span class="hljs-number">32</span> <span class="hljs-number">0</span>d <span class="hljs-number">0</span>a <span class="hljs-number">0</span>d <span class="hljs-number">0</span>a <span class="hljs-number">3</span>c <span class="hljs-number">68</span> <span class="hljs-number">31</span> <span class="hljs-number">3</span>e <span class="hljs-number">48</span> <span class="hljs-number">65</span> <span class="hljs-number">6</span>c <span class="hljs-number">6</span>c <span class="hljs-number">6f</span> | <span class="hljs-number">22.</span>...&lt;h1&gt;Hello|
|<span class="hljs-number">00000030</span>| <span class="hljs-number">2</span>c <span class="hljs-number">20</span> <span class="hljs-number">57</span> <span class="hljs-number">6f</span> <span class="hljs-number">72</span> <span class="hljs-number">6</span>c <span class="hljs-number">64</span> <span class="hljs-number">21</span> <span class="hljs-number">3</span>c <span class="hljs-number">2f</span> <span class="hljs-number">68</span> <span class="hljs-number">31</span> <span class="hljs-number">3</span>e          |, World!&lt;/h1&gt;   |
+--------+-------------------------------------------------+----------------+</code></pre>



<h3 id="自定义协议"><a href="#自定义协议" class="headerlink" title="自定义协议"></a>自定义协议</h3><h4 id="组成要素"><a href="#组成要素" class="headerlink" title="组成要素"></a>组成要素</h4><ul>
<li><strong>魔数</strong>：用来在第一时间判定接收的数据是否为无效数据包</li>
<li><strong>版本号</strong>：可以支持协议的升级</li>
<li><strong>序列化算法</strong>：消息正文到底采用哪种序列化反序列化方式<ul>
<li>如：json、protobuf、hessian、jdk</li>
</ul>
</li>
<li><strong>指令类型</strong>：是登录、注册、单聊、群聊… 跟业务相关</li>
<li><strong>请求序号</strong>：为了双工通信，提供异步能力</li>
<li><strong>正文长度</strong></li>
<li><strong>消息正文</strong></li>
</ul>
<h4 id="编码器与解码器"><a href="#编码器与解码器" class="headerlink" title="编码器与解码器"></a>编码器与解码器</h4><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MessageCodec</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ByteToMessageCodec</span>&lt;<span class="hljs-title">Message</span>&gt; </span>&#123;

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">encode</span><span class="hljs-params">(ChannelHandlerContext ctx, Message msg, ByteBuf out)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        <span class="hljs-comment">// 设置魔数 4个字节</span>
        out.writeBytes(<span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[]&#123;<span class="hljs-string">'N'</span>,<span class="hljs-string">'Y'</span>,<span class="hljs-string">'I'</span>,<span class="hljs-string">'M'</span>&#125;);
        <span class="hljs-comment">// 设置版本号 1个字节</span>
        out.writeByte(<span class="hljs-number">1</span>);
        <span class="hljs-comment">// 设置序列化方式 1个字节</span>
        out.writeByte(<span class="hljs-number">1</span>);
        <span class="hljs-comment">// 设置指令类型 1个字节</span>
        out.writeByte(msg.getMessageType());
        <span class="hljs-comment">// 设置请求序号 4个字节</span>
        out.writeInt(msg.getSequenceId());
        <span class="hljs-comment">// 为了补齐为16个字节，填充1个字节的数据</span>
        out.writeByte(<span class="hljs-number">0xff</span>);

        <span class="hljs-comment">// 获得序列化后的msg</span>
        ByteArrayOutputStream bos = <span class="hljs-keyword">new</span> ByteArrayOutputStream();
        ObjectOutputStream oos = <span class="hljs-keyword">new</span> ObjectOutputStream(bos);
        oos.writeObject(msg);
        <span class="hljs-keyword">byte</span>[] bytes = bos.toByteArray();

        <span class="hljs-comment">// 获得并设置正文长度 长度用4个字节标识</span>
        out.writeInt(bytes.length);
        <span class="hljs-comment">// 设置消息正文</span>
        out.writeBytes(bytes);
    &#125;

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">decode</span><span class="hljs-params">(ChannelHandlerContext ctx, ByteBuf in, List&lt;Object&gt; out)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        <span class="hljs-comment">// 获取魔数</span>
        <span class="hljs-keyword">int</span> magic = in.readInt();
        <span class="hljs-comment">// 获取版本号</span>
        <span class="hljs-keyword">byte</span> version = in.readByte();
        <span class="hljs-comment">// 获得序列化方式</span>
        <span class="hljs-keyword">byte</span> seqType = in.readByte();
        <span class="hljs-comment">// 获得指令类型</span>
        <span class="hljs-keyword">byte</span> messageType = in.readByte();
        <span class="hljs-comment">// 获得请求序号</span>
        <span class="hljs-keyword">int</span> sequenceId = in.readInt();
        <span class="hljs-comment">// 移除补齐字节</span>
        in.readByte();
        <span class="hljs-comment">// 获得正文长度</span>
        <span class="hljs-keyword">int</span> length = in.readInt();
        <span class="hljs-comment">// 获得正文</span>
        <span class="hljs-keyword">byte</span>[] bytes = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[length];
        in.readBytes(bytes, <span class="hljs-number">0</span>, length);
        ObjectInputStream ois = <span class="hljs-keyword">new</span> ObjectInputStream(<span class="hljs-keyword">new</span> ByteArrayInputStream(bytes));
        Message message = (Message) ois.readObject();
		<span class="hljs-comment">// 将信息放入List中，传递给下一个handler</span>
        out.add(message);
        
        <span class="hljs-comment">// 打印获得的信息正文</span>
        System.out.println(<span class="hljs-string">"===========魔数==========="</span>);
        System.out.println(magic);
        System.out.println(<span class="hljs-string">"===========版本号==========="</span>);
        System.out.println(version);
        System.out.println(<span class="hljs-string">"===========序列化方法==========="</span>);
        System.out.println(seqType);
        System.out.println(<span class="hljs-string">"===========指令类型==========="</span>);
        System.out.println(messageType);
        System.out.println(<span class="hljs-string">"===========请求序号==========="</span>);
        System.out.println(sequenceId);
        System.out.println(<span class="hljs-string">"===========正文长度==========="</span>);
        System.out.println(length);
        System.out.println(<span class="hljs-string">"===========正文==========="</span>);
        System.out.println(message);
    &#125;
&#125;</code></pre>

<ul>
<li><p>编码器与解码器方法源于<strong>父类ByteToMessageCodec</strong>，通过该类可以自定义编码器与解码器，<strong>泛型类型为被编码与被解码的类</strong>。此处使用了自定义类Message，代表消息</p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MessageCodec</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ByteToMessageCodec</span>&lt;<span class="hljs-title">Message</span>&gt;</span></code></pre>
</li>
<li><p>编码器<strong>负责将附加信息与正文信息写入到ByteBuf中</strong>，其中附加信息<strong>总字节数最好为2<sup>n</sup>，不足需要补齐</strong>。正文内容如果为对象，需要通过<strong>序列化</strong>将其放入到ByteBuf中</p>
</li>
<li><p>解码器<strong>负责将ByteBuf中的信息取出，并放入List中</strong>，该List用于将信息传递给下一个handler</p>
</li>
</ul>
<p><strong>编写测试类</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestCodec</span> </span>&#123;
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> org.slf4j.Logger log = LoggerFactory.getLogger(StudyServer<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        EmbeddedChannel channel = <span class="hljs-keyword">new</span> EmbeddedChannel();
        <span class="hljs-comment">// 添加解码器，避免粘包半包问题</span>
        channel.pipeline().addLast(<span class="hljs-keyword">new</span> LengthFieldBasedFrameDecoder(<span class="hljs-number">1024</span>, <span class="hljs-number">12</span>, <span class="hljs-number">4</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>));
        channel.pipeline().addLast(<span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG));
        channel.pipeline().addLast(<span class="hljs-keyword">new</span> MessageCodec());
        LoginRequestMessage user = <span class="hljs-keyword">new</span> LoginRequestMessage(<span class="hljs-string">"Nyima"</span>, <span class="hljs-string">"123"</span>);

        <span class="hljs-comment">// 测试编码与解码</span>
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
        <span class="hljs-keyword">new</span> MessageCodec().encode(<span class="hljs-keyword">null</span>, user, byteBuf);
        channel.writeInbound(byteBuf);
    &#125;
&#125;</code></pre>

<ul>
<li>测试类中用到了LengthFieldBasedFrameDecoder，避免粘包半包问题</li>
<li>通过MessageCodec的encode方法将附加信息与正文写入到ByteBuf中，通过channel执行入站操作。入站时会调用decode方法进行解码</li>
</ul>
<p>运行结果</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210427111344.png" srcset="/img/loading.gif" alt=""></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210427134513.png" srcset="/img/loading.gif" alt=""></p>
<h4 id="Sharable注解"><a href="#Sharable注解" class="headerlink" title="@Sharable注解"></a>@Sharable注解</h4><p>为了<strong>提高handler的复用率，可以将handler创建为handler对象</strong>，然后在不同的channel中使用该handler对象进行处理操作</p>
<pre><code class="hljs java">LoggingHandler loggingHandler = <span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG);
<span class="hljs-comment">// 不同的channel中使用同一个handler对象，提高复用率</span>
channel1.pipeline().addLast(loggingHandler);
channel2.pipeline().addLast(loggingHandler);</code></pre>

<p>但是<strong>并不是所有的handler都能通过这种方法来提高复用率的</strong>，例如<code>LengthFieldBasedFrameDecoder</code>。如果多个channel中使用同一个LengthFieldBasedFrameDecoder对象，则可能发生如下问题</p>
<ul>
<li>channel1中收到了一个半包，LengthFieldBasedFrameDecoder发现不是一条完整的数据，则没有继续向下传播</li>
<li>此时channel2中也收到了一个半包，<strong>因为两个channel使用了同一个LengthFieldBasedFrameDecoder，存入其中的数据刚好拼凑成了一个完整的数据包</strong>。LengthFieldBasedFrameDecoder让该数据包继续向下传播，<strong>最终引发错误</strong></li>
</ul>
<p>为了提高handler的复用率，同时又避免出现一些并发问题，<strong>Netty中原生的handler中用<code>@Sharable</code>注解来标明，该handler能否在多个channel中共享。</strong></p>
<p><strong>只有带有该注解，才能通过对象的方式被共享</strong>，否则无法被共享</p>
<h4 id="自定义编解码器能否使用-Sharable注解"><a href="#自定义编解码器能否使用-Sharable注解" class="headerlink" title="自定义编解码器能否使用@Sharable注解"></a><strong>自定义编解码器能否使用@Sharable注解</strong></h4><p><strong>这需要根据自定义的handler的处理逻辑进行分析</strong></p>
<p>我们的MessageCodec本身接收的是LengthFieldBasedFrameDecoder处理之后的数据，那么数据肯定是完整的，按分析来说是可以添加@Sharable注解的</p>
<p>但是实际情况我们并<strong>不能</strong>添加该注解，会抛出异常信息<code>ChannelHandler cn.nyimac.study.day8.protocol.MessageCodec is not allowed to be shared</code></p>
<ul>
<li><p>因为MessageCodec<strong>继承自ByteToMessageCodec</strong>，ByteToMessageCodec类的注解如下</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210427144049.png" srcset="/img/loading.gif" alt=""></p>
<p>这就意味着<strong>ByteToMessageCodec不能被多个channel所共享的</strong></p>
<ul>
<li>原因：<strong>因为该类的目标是：将ByteBuf转化为Message，意味着传进该handler的数据还未被处理过</strong>。所以传过来的ByteBuf<strong>可能并不是完整的数据</strong>，如果共享则会出现问题</li>
</ul>
</li>
</ul>
<p><strong>如果想要共享，需要怎么办呢？</strong></p>
<p>继承<strong>MessageToMessageDecoder</strong>即可。<strong>该类的目标是：将已经被处理的完整数据再次被处理。</strong>传过来的Message<strong>如果是被处理过的完整数据</strong>，那么被共享也就不会出现问题了，也就可以使用@Sharable注解了。实现方式与ByteToMessageCodec类似</p>
<pre><code class="hljs java"><span class="hljs-meta">@ChannelHandler</span>.Sharable
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MessageSharableCodec</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">MessageToMessageCodec</span>&lt;<span class="hljs-title">ByteBuf</span>, <span class="hljs-title">Message</span>&gt; </span>&#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">encode</span><span class="hljs-params">(ChannelHandlerContext ctx, Message msg, List&lt;Object&gt; out)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        ...
    &#125;

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">decode</span><span class="hljs-params">(ChannelHandlerContext ctx, ByteBuf msg, List&lt;Object&gt; out)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
		...
    &#125;
&#125;</code></pre>



<h2 id="3、在线聊天室"><a href="#3、在线聊天室" class="headerlink" title="3、在线聊天室"></a>3、在线聊天室</h2><h3 id="聊天室业务"><a href="#聊天室业务" class="headerlink" title="聊天室业务"></a>聊天室业务</h3><h4 id="用户登录接口"><a href="#用户登录接口" class="headerlink" title="用户登录接口"></a>用户登录接口</h4><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">UserService</span> </span>&#123;

    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 登录</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> username 用户名</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> password 密码</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 登录成功返回 true, 否则返回 false</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-function"><span class="hljs-keyword">boolean</span> <span class="hljs-title">login</span><span class="hljs-params">(String username, String password)</span></span>;
&#125;</code></pre>



<h4 id="用户会话接口"><a href="#用户会话接口" class="headerlink" title="用户会话接口"></a>用户会话接口</h4><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">Session</span> </span>&#123;

    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 绑定会话</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> channel 哪个 channel 要绑定会话</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> username 会话绑定用户</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">bind</span><span class="hljs-params">(Channel channel, String username)</span></span>;

    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 解绑会话</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> channel 哪个 channel 要解绑会话</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">unbind</span><span class="hljs-params">(Channel channel)</span></span>;

    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 获取属性</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> channel 哪个 channel</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> name 属性名</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 属性值</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-function">Object <span class="hljs-title">getAttribute</span><span class="hljs-params">(Channel channel, String name)</span></span>;

    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 设置属性</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> channel 哪个 channel</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> name 属性名</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> value 属性值</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setAttribute</span><span class="hljs-params">(Channel channel, String name, Object value)</span></span>;

    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 根据用户名获取 channel</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> username 用户名</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@return</span> channel</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-function">Channel <span class="hljs-title">getChannel</span><span class="hljs-params">(String username)</span></span>;
&#125;</code></pre>



<h4 id="群聊会话接口"><a href="#群聊会话接口" class="headerlink" title="群聊会话接口"></a>群聊会话接口</h4><pre><code class="hljs dart">public <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">GroupSession</span> </span>&#123;

    <span class="hljs-comment"><span class="markdown">/**</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>创建一个聊天组, 如果不存在才能创建成功, 否则返回 null</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@param name 组名</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@param members 成员</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@return 成功时返回组对象, 失败返回 null</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-code">     */</span></span></span>
    Group createGroup(<span class="hljs-built_in">String</span> name, <span class="hljs-built_in">Set</span>&lt;<span class="hljs-built_in">String</span>&gt; members);

    <span class="hljs-comment"><span class="markdown">/**</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>加入聊天组</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@param name 组名</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@param member 成员名</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@return 如果组不存在返回 null, 否则返回组对象</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-code">     */</span></span></span>
    Group joinMember(<span class="hljs-built_in">String</span> name, <span class="hljs-built_in">String</span> member);

    <span class="hljs-comment"><span class="markdown">/**</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>移除组成员</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@param name 组名</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@param member 成员名</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@return 如果组不存在返回 null, 否则返回组对象</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-code">     */</span></span></span>
    Group removeMember(<span class="hljs-built_in">String</span> name, <span class="hljs-built_in">String</span> member);

    <span class="hljs-comment"><span class="markdown">/**</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>移除聊天组</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@param name 组名</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@return 如果组不存在返回 null, 否则返回组对象</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-code">     */</span></span></span>
    Group removeGroup(<span class="hljs-built_in">String</span> name);

    <span class="hljs-comment"><span class="markdown">/**</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>获取组成员</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@param name 组名</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@return 成员集合, 如果群不存在或没有成员会返回 empty set</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-code">     */</span></span></span>
    <span class="hljs-built_in">Set</span>&lt;<span class="hljs-built_in">String</span>&gt; getMembers(<span class="hljs-built_in">String</span> name);

    <span class="hljs-comment"><span class="markdown">/**</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>获取组成员的 channel 集合, 只有在线的 channel 才会返回</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@param name 组名</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@return 成员 channel 集合</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-code">     */</span></span></span>
    <span class="hljs-built_in">List</span>&lt;Channel&gt; getMembersChannel(<span class="hljs-built_in">String</span> name);
    
    <span class="hljs-comment"><span class="markdown">/**</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>判断群聊是否一被创建</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@param name 群聊名称</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-bullet">     * </span>@return 是否存在</span></span>
<span class="hljs-comment"><span class="markdown"><span class="hljs-code">     */</span></span></span>
    boolean isCreated(<span class="hljs-built_in">String</span> name);
&#125;</code></pre>



<h4 id="整体结构"><a href="#整体结构" class="headerlink" title="整体结构"></a>整体结构</h4><p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210428154749.png" srcset="/img/loading.gif" alt=""></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210428154801.png" srcset="/img/loading.gif" alt=""></p>
<ul>
<li><p>client包：存放客户端相关类</p>
</li>
<li><p>message包：存放各种类型的消息</p>
</li>
<li><p>protocol包：存放自定义协议</p>
</li>
<li><p>server包：存放服务器相关类</p>
<ul>
<li>service包：存放用户相关类</li>
<li>session包：单聊及群聊相关会话类</li>
</ul>
</li>
</ul>
<h4 id="客户端代码结构"><a href="#客户端代码结构" class="headerlink" title="客户端代码结构"></a>客户端代码结构</h4><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChatClient</span> </span>&#123;
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Logger log = LoggerFactory.getLogger(ChatClient<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        NioEventLoopGroup group = <span class="hljs-keyword">new</span> NioEventLoopGroup();
        LoggingHandler loggingHandler = <span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG);
        MessageSharableCodec messageSharableCodec = <span class="hljs-keyword">new</span> MessageSharableCodec();
        <span class="hljs-keyword">try</span> &#123;
            Bootstrap bootstrap = <span class="hljs-keyword">new</span> Bootstrap();
            bootstrap.group(group);
            bootstrap.channel(NioSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
            bootstrap.handler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123;
                <span class="hljs-meta">@Override</span>
                <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                    ch.pipeline().addLast(<span class="hljs-keyword">new</span> ProtocolFrameDecoder());
                    ch.pipeline().addLast(loggingHandler);
                    ch.pipeline().addLast(messageSharableCodec);
                &#125;
            &#125;);
            Channel channel = bootstrap.connect().sync().channel();
            channel.closeFuture().sync();
        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;
            e.printStackTrace();
        &#125; <span class="hljs-keyword">finally</span> &#123;
           group.shutdownGracefully();
        &#125;
    &#125;
&#125;</code></pre>



<h4 id="服务器代码结构"><a href="#服务器代码结构" class="headerlink" title="服务器代码结构"></a>服务器代码结构</h4><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChatServer</span> </span>&#123;
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Logger log = LoggerFactory.getLogger(ChatServer<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        NioEventLoopGroup boss = <span class="hljs-keyword">new</span> NioEventLoopGroup();
        NioEventLoopGroup worker = <span class="hljs-keyword">new</span> NioEventLoopGroup();
        LoggingHandler loggingHandler = <span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG);
        MessageSharableCodec messageSharableCodec = <span class="hljs-keyword">new</span> MessageSharableCodec();
        <span class="hljs-keyword">try</span> &#123;
            ServerBootstrap bootstrap = <span class="hljs-keyword">new</span> ServerBootstrap();
            bootstrap.group(boss, worker);
            bootstrap.channel(NioServerSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
            bootstrap.childHandler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123;
                <span class="hljs-meta">@Override</span>
                <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                    ch.pipeline().addLast(<span class="hljs-keyword">new</span> ProtocolFrameDecoder());
                    ch.pipeline().addLast(loggingHandler);
                    ch.pipeline().addLast(messageSharableCodec);
                &#125;
            &#125;);
            Channel channel = bootstrap.bind(<span class="hljs-number">8080</span>).sync().channel();
            channel.closeFuture().sync();
        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;
            e.printStackTrace();
        &#125; <span class="hljs-keyword">finally</span> &#123;
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        &#125;
    &#125;
&#125;</code></pre>



<h3 id="登录"><a href="#登录" class="headerlink" title="登录"></a>登录</h3><h4 id="客户端代码-1"><a href="#客户端代码-1" class="headerlink" title="客户端代码"></a>客户端代码</h4><p>客户端添加如下handler，<strong>分别处理登录、聊天等操作</strong></p>
<pre><code class="hljs java">ch.pipeline().addLast(<span class="hljs-keyword">new</span> ChannelInboundHandlerAdapter() &#123;
    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 创建连接时执行的处理器，用于执行登陆操作</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelActive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        <span class="hljs-comment">// 开辟额外线程，用于用户登陆及后续操作</span>
        <span class="hljs-keyword">new</span> Thread(()-&gt;&#123;
            Scanner scanner = <span class="hljs-keyword">new</span> Scanner(System.in);
            System.out.println(<span class="hljs-string">"请输入用户名"</span>);
            String username = scanner.next();
            System.out.println(<span class="hljs-string">"请输入密码"</span>);
            String password = scanner.next();
            <span class="hljs-comment">// 创建包含登录信息的请求体</span>
            LoginRequestMessage message = <span class="hljs-keyword">new</span> LoginRequestMessage(username, password);
            <span class="hljs-comment">// 发送到channel中</span>
            ctx.writeAndFlush(message);
            System.out.println(<span class="hljs-string">"等待后续操作..."</span>);
            <span class="hljs-comment">// 阻塞，直到登陆成功后CountDownLatch被设置为0</span>
            <span class="hljs-keyword">try</span> &#123;
                waitLogin.await();
            &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;
                e.printStackTrace();
            &#125;
            <span class="hljs-comment">// 执行后续操作</span>
            <span class="hljs-keyword">if</span> (!loginStatus.get()) &#123;
                <span class="hljs-comment">// 登陆失败，关闭channel并返回</span>
                ctx.channel().close();
                <span class="hljs-keyword">return</span>;
            &#125;
            <span class="hljs-comment">// 登录成功后，执行其他操作</span>
            <span class="hljs-keyword">while</span> (<span class="hljs-keyword">true</span>) &#123;
                System.out.println(<span class="hljs-string">"=================================="</span>);
                System.out.println(<span class="hljs-string">"send [username] [content]"</span>);
                System.out.println(<span class="hljs-string">"gsend [group name] [content]"</span>);
                System.out.println(<span class="hljs-string">"gcreate [group name] [m1,m2,m3...]"</span>);
                System.out.println(<span class="hljs-string">"gmembers [group name]"</span>);
                System.out.println(<span class="hljs-string">"gjoin [group name]"</span>);
                System.out.println(<span class="hljs-string">"gquit [group name]"</span>);
                System.out.println(<span class="hljs-string">"quit"</span>);
                System.out.println(<span class="hljs-string">"=================================="</span>);
                String command = scanner.nextLine();
                <span class="hljs-comment">// 获得指令及其参数，并发送对应类型消息</span>
                String[] commands = command.split(<span class="hljs-string">" "</span>);
                <span class="hljs-keyword">switch</span> (commands[<span class="hljs-number">0</span>])&#123;
                    <span class="hljs-keyword">case</span> <span class="hljs-string">"send"</span>:
                        ctx.writeAndFlush(<span class="hljs-keyword">new</span> ChatRequestMessage(username, commands[<span class="hljs-number">1</span>], commands[<span class="hljs-number">2</span>]));
                        <span class="hljs-keyword">break</span>;
                    <span class="hljs-keyword">case</span> <span class="hljs-string">"gsend"</span>:
                        ctx.writeAndFlush(<span class="hljs-keyword">new</span> GroupChatRequestMessage(username,commands[<span class="hljs-number">1</span>], commands[<span class="hljs-number">2</span>]));
                        <span class="hljs-keyword">break</span>;
                    <span class="hljs-keyword">case</span> <span class="hljs-string">"gcreate"</span>:
                        <span class="hljs-comment">// 分割，获得群员名</span>
                        String[] members = commands[<span class="hljs-number">2</span>].split(<span class="hljs-string">","</span>);
                        Set&lt;String&gt; set = <span class="hljs-keyword">new</span> HashSet&lt;&gt;(Arrays.asList(members));
                        <span class="hljs-comment">// 把自己加入到群聊中</span>
                        set.add(username);
                        ctx.writeAndFlush(<span class="hljs-keyword">new</span> GroupCreateRequestMessage(commands[<span class="hljs-number">1</span>],set));
                        <span class="hljs-keyword">break</span>;
                    <span class="hljs-keyword">case</span> <span class="hljs-string">"gmembers"</span>:
                        ctx.writeAndFlush(<span class="hljs-keyword">new</span> GroupMembersRequestMessage(commands[<span class="hljs-number">1</span>]));
                        <span class="hljs-keyword">break</span>;
                    <span class="hljs-keyword">case</span> <span class="hljs-string">"gjoin"</span>:
                        ctx.writeAndFlush(<span class="hljs-keyword">new</span> GroupJoinRequestMessage(username, commands[<span class="hljs-number">1</span>]));
                        <span class="hljs-keyword">break</span>;
                    <span class="hljs-keyword">case</span> <span class="hljs-string">"gquit"</span>:
                        ctx.writeAndFlush(<span class="hljs-keyword">new</span> GroupQuitRequestMessage(username, commands[<span class="hljs-number">1</span>]));
                        <span class="hljs-keyword">break</span>;
                    <span class="hljs-keyword">case</span> <span class="hljs-string">"quit"</span>:
                        ctx.channel().close();
                        <span class="hljs-keyword">return</span>;
                    <span class="hljs-keyword">default</span>:
                        System.out.println(<span class="hljs-string">"指令有误，请重新输入"</span>);
                        <span class="hljs-keyword">continue</span>;
                &#125;
            &#125;
        &#125;, <span class="hljs-string">"login channel"</span>).start();
    &#125;

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        log.debug(<span class="hljs-string">"&#123;&#125;"</span>, msg);
        <span class="hljs-keyword">if</span> (msg <span class="hljs-keyword">instanceof</span> LoginResponseMessage) &#123;
            <span class="hljs-comment">// 如果是登录响应信息</span>
            LoginResponseMessage message = (LoginResponseMessage) msg;
            <span class="hljs-keyword">boolean</span> isSuccess = message.isSuccess();
            <span class="hljs-comment">// 登录成功，设置登陆标记</span>
            <span class="hljs-keyword">if</span> (isSuccess) &#123;
                loginStatus.set(<span class="hljs-keyword">true</span>);
            &#125;
            <span class="hljs-comment">// 登陆后，唤醒登陆线程</span>
            waitLogin.countDown();
        &#125;
    &#125;
&#125;);</code></pre>



<h4 id="服务器代码-2"><a href="#服务器代码-2" class="headerlink" title="服务器代码"></a>服务器代码</h4><p>服务器添加如下handler，并添加到对应的channel中，<strong>负责处理登录请求信息，并作出响应</strong></p>
<pre><code class="hljs java"><span class="hljs-meta">@ChannelHandler</span>.Sharable <span class="hljs-comment">// 必须添加该注解</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoginRequestMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler</span>&lt;<span class="hljs-title">LoginRequestMessage</span>&gt; </span>&#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext ctx, LoginRequestMessage msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        <span class="hljs-comment">// 获得登录信息</span>
        String username = msg.getUsername();
        String password = msg.getPassword();
        <span class="hljs-comment">// 校验登录信息</span>
        <span class="hljs-keyword">boolean</span> login = UserServiceFactory.getUserService().login(username, password);
        LoginResponseMessage message;
        <span class="hljs-keyword">if</span> (login) &#123;
            message = <span class="hljs-keyword">new</span> LoginResponseMessage(<span class="hljs-keyword">true</span>, <span class="hljs-string">"登陆成功"</span>);
            <span class="hljs-comment">// 绑定channel与user</span>
            SessionFactory.getSession().bind(ctx.channel(), username);
        &#125; <span class="hljs-keyword">else</span> &#123;
            message = <span class="hljs-keyword">new</span> LoginResponseMessage(<span class="hljs-keyword">false</span>, <span class="hljs-string">"登陆失败"</span>);
        &#125;
        ctx.writeAndFlush(message);
    &#125;
&#125;</code></pre>

<pre><code class="hljs java"><span class="hljs-comment">// 该handler处理登录请求</span>
LoginRequestMessageHandler loginRequestMessageHandler = <span class="hljs-keyword">new</span> LoginRequestMessageHandler();
ch.pipeline().addLast(<span class="hljs-keyword">new</span> LoginRequestMessageHandler());</code></pre>



<h4 id="运行结果"><a href="#运行结果" class="headerlink" title="运行结果"></a>运行结果</h4><p><strong>客户端</strong></p>
<pre><code class="hljs java"><span class="hljs-number">5665</span> [nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.protocol.MessageSharableCodec  - <span class="hljs-number">1314474317</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">279</span>
<span class="hljs-number">5667</span> [nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.protocol.MessageSharableCodec  - message:AbstractResponseMessage&#123;success=<span class="hljs-keyword">true</span>, reason=<span class="hljs-string">'登陆成功'</span>&#125;
<span class="hljs-number">5667</span> [nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=<span class="hljs-keyword">true</span>, reason=<span class="hljs-string">'登陆成功'</span>&#125;
success</code></pre>



<p><strong>服务器</strong></p>
<pre><code class="hljs angelscript"><span class="hljs-number">11919</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG cn.nyimac.study.day8.protocol.MessageSharableCodec  - <span class="hljs-number">1314474317</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">217</span>
<span class="hljs-number">11919</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG cn.nyimac.study.day8.protocol.MessageSharableCodec  - message:LoginRequestMessage&#123;username=<span class="hljs-string">'Nyima'</span>, password=<span class="hljs-string">'123'</span>&#125;

<span class="hljs-number">7946</span> [nioEventLoopGroup<span class="hljs-number">-3</span><span class="hljs-number">-1</span>] DEBUG io.netty.handler.logging.LoggingHandler  - [id: <span class="hljs-number">0x8e7c07f6</span>, L:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">8080</span> - R:/<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">60572</span>] WRITE: <span class="hljs-number">295</span>B
         +-------------------------------------------------+
         |  <span class="hljs-number">0</span>  <span class="hljs-number">1</span>  <span class="hljs-number">2</span>  <span class="hljs-number">3</span>  <span class="hljs-number">4</span>  <span class="hljs-number">5</span>  <span class="hljs-number">6</span>  <span class="hljs-number">7</span>  <span class="hljs-number">8</span>  <span class="hljs-number">9</span>  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|<span class="hljs-number">00000000</span>| <span class="hljs-number">4</span>e <span class="hljs-number">59</span> <span class="hljs-number">49</span> <span class="hljs-number">4</span>d <span class="hljs-number">01</span> <span class="hljs-number">01</span> <span class="hljs-number">01</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> ff <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">17</span> |NYIM............|
|<span class="hljs-number">00000010</span>| ac ed <span class="hljs-number">00</span> <span class="hljs-number">05</span> <span class="hljs-number">73</span> <span class="hljs-number">72</span> <span class="hljs-number">00</span> <span class="hljs-number">31</span> <span class="hljs-number">63</span> <span class="hljs-number">6</span>e <span class="hljs-number">2</span>e <span class="hljs-number">6</span>e <span class="hljs-number">79</span> <span class="hljs-number">69</span> <span class="hljs-number">6</span>d <span class="hljs-number">61</span> |....sr<span class="hljs-number">.1</span>cn.nyima|
|<span class="hljs-number">00000020</span>| <span class="hljs-number">63</span> <span class="hljs-number">2</span>e <span class="hljs-number">73</span> <span class="hljs-number">74</span> <span class="hljs-number">75</span> <span class="hljs-number">64</span> <span class="hljs-number">79</span> <span class="hljs-number">2</span>e <span class="hljs-number">64</span> <span class="hljs-number">61</span> <span class="hljs-number">79</span> <span class="hljs-number">38</span> <span class="hljs-number">2</span>e <span class="hljs-number">6</span>d <span class="hljs-number">65</span> <span class="hljs-number">73</span> |c.study.day8.mes|
|<span class="hljs-number">00000030</span>| <span class="hljs-number">73</span> <span class="hljs-number">61</span> <span class="hljs-number">67</span> <span class="hljs-number">65</span> <span class="hljs-number">2</span>e <span class="hljs-number">4</span>c <span class="hljs-number">6f</span> <span class="hljs-number">67</span> <span class="hljs-number">69</span> <span class="hljs-number">6</span>e <span class="hljs-number">52</span> <span class="hljs-number">65</span> <span class="hljs-number">73</span> <span class="hljs-number">70</span> <span class="hljs-number">6f</span> <span class="hljs-number">6</span>e |sage.LoginRespon|
|<span class="hljs-number">00000040</span>| <span class="hljs-number">73</span> <span class="hljs-number">65</span> <span class="hljs-number">4</span>d <span class="hljs-number">65</span> <span class="hljs-number">73</span> <span class="hljs-number">73</span> <span class="hljs-number">61</span> <span class="hljs-number">67</span> <span class="hljs-number">65</span> e2 <span class="hljs-number">34</span> <span class="hljs-number">49</span> <span class="hljs-number">24</span> <span class="hljs-number">72</span> <span class="hljs-number">52</span> f3 |seMessage<span class="hljs-number">.4</span>I$rR.|
|<span class="hljs-number">00000050</span>| <span class="hljs-number">07</span> <span class="hljs-number">02</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">78</span> <span class="hljs-number">72</span> <span class="hljs-number">00</span> <span class="hljs-number">34</span> <span class="hljs-number">63</span> <span class="hljs-number">6</span>e <span class="hljs-number">2</span>e <span class="hljs-number">6</span>e <span class="hljs-number">79</span> <span class="hljs-number">69</span> <span class="hljs-number">6</span>d <span class="hljs-number">61</span> |....xr<span class="hljs-number">.4</span>cn.nyima|
|<span class="hljs-number">00000060</span>| <span class="hljs-number">63</span> <span class="hljs-number">2</span>e <span class="hljs-number">73</span> <span class="hljs-number">74</span> <span class="hljs-number">75</span> <span class="hljs-number">64</span> <span class="hljs-number">79</span> <span class="hljs-number">2</span>e <span class="hljs-number">64</span> <span class="hljs-number">61</span> <span class="hljs-number">79</span> <span class="hljs-number">38</span> <span class="hljs-number">2</span>e <span class="hljs-number">6</span>d <span class="hljs-number">65</span> <span class="hljs-number">73</span> |c.study.day8.mes|
|<span class="hljs-number">00000070</span>| <span class="hljs-number">73</span> <span class="hljs-number">61</span> <span class="hljs-number">67</span> <span class="hljs-number">65</span> <span class="hljs-number">2</span>e <span class="hljs-number">41</span> <span class="hljs-number">62</span> <span class="hljs-number">73</span> <span class="hljs-number">74</span> <span class="hljs-number">72</span> <span class="hljs-number">61</span> <span class="hljs-number">63</span> <span class="hljs-number">74</span> <span class="hljs-number">52</span> <span class="hljs-number">65</span> <span class="hljs-number">73</span> |sage.AbstractRes|
|<span class="hljs-number">00000080</span>| <span class="hljs-number">70</span> <span class="hljs-number">6f</span> <span class="hljs-number">6</span>e <span class="hljs-number">73</span> <span class="hljs-number">65</span> <span class="hljs-number">4</span>d <span class="hljs-number">65</span> <span class="hljs-number">73</span> <span class="hljs-number">73</span> <span class="hljs-number">61</span> <span class="hljs-number">67</span> <span class="hljs-number">65</span> b3 <span class="hljs-number">7</span>e <span class="hljs-number">19</span> <span class="hljs-number">32</span> |ponseMessage.~<span class="hljs-number">.2</span>|
|<span class="hljs-number">00000090</span>| <span class="hljs-number">9</span>b <span class="hljs-number">88</span> <span class="hljs-number">4</span>d <span class="hljs-number">7</span>b <span class="hljs-number">02</span> <span class="hljs-number">00</span> <span class="hljs-number">02</span> <span class="hljs-number">5</span>a <span class="hljs-number">00</span> <span class="hljs-number">07</span> <span class="hljs-number">73</span> <span class="hljs-number">75</span> <span class="hljs-number">63</span> <span class="hljs-number">63</span> <span class="hljs-number">65</span> <span class="hljs-number">73</span> |..M&#123;...Z..succes|
|<span class="hljs-number">000000</span>a0| <span class="hljs-number">73</span> <span class="hljs-number">4</span>c <span class="hljs-number">00</span> <span class="hljs-number">06</span> <span class="hljs-number">72</span> <span class="hljs-number">65</span> <span class="hljs-number">61</span> <span class="hljs-number">73</span> <span class="hljs-number">6f</span> <span class="hljs-number">6</span>e <span class="hljs-number">74</span> <span class="hljs-number">00</span> <span class="hljs-number">12</span> <span class="hljs-number">4</span>c <span class="hljs-number">6</span>a <span class="hljs-number">61</span> |sL..reasont..Lja|
|<span class="hljs-number">000000</span>b0| <span class="hljs-number">76</span> <span class="hljs-number">61</span> <span class="hljs-number">2f</span> <span class="hljs-number">6</span>c <span class="hljs-number">61</span> <span class="hljs-number">6</span>e <span class="hljs-number">67</span> <span class="hljs-number">2f</span> <span class="hljs-number">53</span> <span class="hljs-number">74</span> <span class="hljs-number">72</span> <span class="hljs-number">69</span> <span class="hljs-number">6</span>e <span class="hljs-number">67</span> <span class="hljs-number">3</span>b <span class="hljs-number">78</span> |va/lang/String;x|
|<span class="hljs-number">000000</span>c0| <span class="hljs-number">72</span> <span class="hljs-number">00</span> <span class="hljs-number">24</span> <span class="hljs-number">63</span> <span class="hljs-number">6</span>e <span class="hljs-number">2</span>e <span class="hljs-number">6</span>e <span class="hljs-number">79</span> <span class="hljs-number">69</span> <span class="hljs-number">6</span>d <span class="hljs-number">61</span> <span class="hljs-number">63</span> <span class="hljs-number">2</span>e <span class="hljs-number">73</span> <span class="hljs-number">74</span> <span class="hljs-number">75</span> |r.$cn.nyimac.stu|
|<span class="hljs-number">000000</span>d0| <span class="hljs-number">64</span> <span class="hljs-number">79</span> <span class="hljs-number">2</span>e <span class="hljs-number">64</span> <span class="hljs-number">61</span> <span class="hljs-number">79</span> <span class="hljs-number">38</span> <span class="hljs-number">2</span>e <span class="hljs-number">6</span>d <span class="hljs-number">65</span> <span class="hljs-number">73</span> <span class="hljs-number">73</span> <span class="hljs-number">61</span> <span class="hljs-number">67</span> <span class="hljs-number">65</span> <span class="hljs-number">2</span>e |dy.day8.message.|
|<span class="hljs-number">000000e0</span>| <span class="hljs-number">4</span>d <span class="hljs-number">65</span> <span class="hljs-number">73</span> <span class="hljs-number">73</span> <span class="hljs-number">61</span> <span class="hljs-number">67</span> <span class="hljs-number">65</span> dd e9 <span class="hljs-number">84</span> b7 <span class="hljs-number">21</span> db <span class="hljs-number">18</span> <span class="hljs-number">52</span> <span class="hljs-number">02</span> |Message....!..R.|
|<span class="hljs-number">000000f</span>0| <span class="hljs-number">00</span> <span class="hljs-number">02</span> <span class="hljs-number">49</span> <span class="hljs-number">00</span> <span class="hljs-number">0</span>b <span class="hljs-number">6</span>d <span class="hljs-number">65</span> <span class="hljs-number">73</span> <span class="hljs-number">73</span> <span class="hljs-number">61</span> <span class="hljs-number">67</span> <span class="hljs-number">65</span> <span class="hljs-number">54</span> <span class="hljs-number">79</span> <span class="hljs-number">70</span> <span class="hljs-number">65</span> |..I..messageType|
|<span class="hljs-number">00000100</span>| <span class="hljs-number">49</span> <span class="hljs-number">00</span> <span class="hljs-number">0</span>a <span class="hljs-number">73</span> <span class="hljs-number">65</span> <span class="hljs-number">71</span> <span class="hljs-number">75</span> <span class="hljs-number">65</span> <span class="hljs-number">6</span>e <span class="hljs-number">63</span> <span class="hljs-number">65</span> <span class="hljs-number">49</span> <span class="hljs-number">64</span> <span class="hljs-number">78</span> <span class="hljs-number">70</span> <span class="hljs-number">00</span> |I..sequenceIdxp.|
|<span class="hljs-number">00000110</span>| <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">00</span> <span class="hljs-number">01</span> <span class="hljs-number">74</span> <span class="hljs-number">00</span> <span class="hljs-number">0</span>c e7 <span class="hljs-number">99</span> bb e9 <span class="hljs-number">99</span> |........t.......|
|<span class="hljs-number">00000120</span>| <span class="hljs-number">86</span> e6 <span class="hljs-number">88</span> <span class="hljs-number">90</span> e5 <span class="hljs-number">8</span>a <span class="hljs-number">9f</span>                            |.......         |
+--------+-------------------------------------------------+----------------+</code></pre>



<h3 id="单聊"><a href="#单聊" class="headerlink" title="单聊"></a>单聊</h3><p>客户端输入<code>send username content</code>即可发送单聊消息，需要<strong>服务器端添加处理ChatRequestMessage的handler</strong></p>
<pre><code class="hljs java"><span class="hljs-meta">@ChannelHandler</span>.Sharable <span class="hljs-comment">// 必须添加该注解</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChatRequestMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler</span>&lt;<span class="hljs-title">ChatRequestMessage</span>&gt; </span>&#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext ctx, ChatRequestMessage msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        <span class="hljs-comment">// 获得user所在的channel</span>
        Channel channel = SessionFactory.getSession().getChannel(msg.getTo());
        <span class="hljs-comment">// 如果双方都在线</span>
        <span class="hljs-keyword">if</span> (channel != <span class="hljs-keyword">null</span>) &#123;
            <span class="hljs-comment">// 通过接收方与服务器之间的channel发送信息</span>
            channel.writeAndFlush(<span class="hljs-keyword">new</span> ChatResponseMessage(msg.getFrom(), msg.getContent()));
        &#125; <span class="hljs-keyword">else</span> &#123;
            <span class="hljs-comment">// 通过发送方与服务器之间的channel发送消息</span>
            ctx.writeAndFlush(<span class="hljs-keyword">new</span> ChatResponseMessage(<span class="hljs-keyword">false</span>, <span class="hljs-string">"对方用户不存在或离线，发送失败"</span>));
        &#125;
    &#125;
&#125;</code></pre>

<pre><code class="hljs java"><span class="hljs-comment">// 该handler处理单聊请求</span>
ChatRequestMessageHandler chatRequestMessageHandler = <span class="hljs-keyword">new</span> ChatRequestMessageHandler();
ch.pipeline().addLast(chatRequestMessageHandler);</code></pre>

<p><strong>运行结果</strong></p>
<p>发送方（zhangsan）</p>
<pre><code class="hljs autoit"><span class="hljs-built_in">send</span> Nyima hello</code></pre>

<p>接收方（Nyima）</p>
<pre><code class="hljs java"><span class="hljs-comment">// 收到zhangsan发来的消息</span>
<span class="hljs-number">20230</span> [nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - ChatResponseMessage&#123;from=<span class="hljs-string">'zhangsan'</span>, content=<span class="hljs-string">'hello'</span>&#125;</code></pre>



<h3 id="群聊"><a href="#群聊" class="headerlink" title="群聊"></a>群聊</h3><h4 id="创建-1"><a href="#创建-1" class="headerlink" title="创建"></a>创建</h4><p>添加处理<code>GroupCreateRequestMessage</code>的handler</p>
<pre><code class="hljs java"><span class="hljs-meta">@ChannelHandler</span>.Sharable
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GroupCreateMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler</span>&lt;<span class="hljs-title">GroupCreateRequestMessage</span>&gt; </span>&#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext ctx, GroupCreateRequestMessage msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        <span class="hljs-comment">// 获得要创建的群聊名</span>
        String groupName = msg.getGroupName();
        <span class="hljs-comment">// 获得要创建的群聊的成员组</span>
        Set&lt;String&gt; members = msg.getMembers();
        <span class="hljs-comment">// 判断该群聊是否创建过，未创建返回null并创建群聊</span>
        Group group = GroupSessionFactory.getGroupSession().createGroup(groupName, members);
        <span class="hljs-keyword">if</span> (group == <span class="hljs-keyword">null</span>) &#123;
            <span class="hljs-comment">// 发送创建成功消息</span>
            GroupCreateResponseMessage groupCreateResponseMessage = <span class="hljs-keyword">new</span> GroupCreateResponseMessage(<span class="hljs-keyword">true</span>, groupName + <span class="hljs-string">"创建成功"</span>);
            ctx.writeAndFlush(groupCreateResponseMessage);
            <span class="hljs-comment">// 获得在线群员的channel，给群员发送入群聊消息</span>
            List&lt;Channel&gt; membersChannel = GroupSessionFactory.getGroupSession().getMembersChannel(groupName);
            groupCreateResponseMessage = <span class="hljs-keyword">new</span> GroupCreateResponseMessage(<span class="hljs-keyword">true</span>, <span class="hljs-string">"您已被拉入"</span>+groupName);
            <span class="hljs-comment">// 给每个在线群员发送消息</span>
            <span class="hljs-keyword">for</span>(Channel channel : membersChannel) &#123;
                channel.writeAndFlush(groupCreateResponseMessage);
            &#125;
        &#125; <span class="hljs-keyword">else</span> &#123;
            <span class="hljs-comment">// 发送失败消息</span>
            GroupCreateResponseMessage groupCreateResponseMessage = <span class="hljs-keyword">new</span> GroupCreateResponseMessage(<span class="hljs-keyword">false</span>, groupName + <span class="hljs-string">"已存在"</span>);
            ctx.writeAndFlush(groupCreateResponseMessage);
        &#125;
    &#125;
&#125;</code></pre>

<pre><code class="hljs java"><span class="hljs-comment">// 该handler处理创建群聊请求</span>
GroupCreateMessageHandler groupCreateMessageHandler = <span class="hljs-keyword">new</span> GroupCreateMessageHandler();
ch.pipeline().addLast(groupCreateMessageHandler);</code></pre>



<p><strong>运行结果</strong></p>
<p>创建者客户端</p>
<pre><code class="hljs java"><span class="hljs-comment">// 首次创建</span>
gcreate Netty学习 zhangsan,lisi

<span class="hljs-number">31649</span> [nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=<span class="hljs-keyword">true</span>, reason=<span class="hljs-string">'Netty学习创建成功'</span>&#125;
<span class="hljs-number">15244</span> [nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=<span class="hljs-keyword">true</span>, reason=<span class="hljs-string">'您已被拉入Netty学习'</span>&#125;


<span class="hljs-comment">// 再次创建</span>
gcreate Netty学习 zhangsan,lisi
<span class="hljs-number">40771</span> [nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=<span class="hljs-keyword">false</span>, reason=<span class="hljs-string">'Netty学习已存在'</span>&#125;</code></pre>



<p>群员客户端</p>
<pre><code class="hljs java"><span class="hljs-number">28788</span> [nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=<span class="hljs-keyword">true</span>, reason=<span class="hljs-string">'您已被拉入Netty学习'</span>&#125;</code></pre>



<h4 id="聊天"><a href="#聊天" class="headerlink" title="聊天"></a>聊天</h4><pre><code class="hljs java"><span class="hljs-meta">@ChannelHandler</span>.Sharable
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GroupChatMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler</span>&lt;<span class="hljs-title">GroupChatRequestMessage</span>&gt; </span>&#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext ctx, GroupChatRequestMessage msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        String groupName = msg.getGroupName();
        GroupSession groupSession = GroupSessionFactory.getGroupSession();
        <span class="hljs-comment">// 判断群聊是否存在</span>
        <span class="hljs-keyword">boolean</span> isCreated = groupSession.isCreated(groupName);
        <span class="hljs-keyword">if</span> (isCreated) &#123;
            <span class="hljs-comment">// 给群员发送信息</span>
            List&lt;Channel&gt; membersChannel = groupSession.getMembersChannel(groupName);
            <span class="hljs-keyword">for</span>(Channel channel : membersChannel) &#123;
                channel.writeAndFlush(<span class="hljs-keyword">new</span> GroupChatResponseMessage(msg.getFrom(), msg.getContent()));
            &#125;
        &#125; <span class="hljs-keyword">else</span> &#123;
            ctx.writeAndFlush(<span class="hljs-keyword">new</span> GroupChatResponseMessage(<span class="hljs-keyword">false</span>, <span class="hljs-string">"群聊不存在"</span>));
        &#125;
    &#125;
&#125;</code></pre>

<pre><code class="hljs java"><span class="hljs-comment">// 该handler处理群聊聊天</span>
GroupChatMessageHandler groupChatMessageHandler = <span class="hljs-keyword">new</span> GroupChatMessageHandler();
ch.pipeline().addLast(groupChatMessageHandler);</code></pre>



<p><strong>运行结果</strong></p>
<p>发送方（群聊存在）</p>
<pre><code class="hljs stylus">gsend Netty学习 你们好

<span class="hljs-number">45408</span> [nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn<span class="hljs-selector-class">.nyimac</span><span class="hljs-selector-class">.study</span><span class="hljs-selector-class">.day8</span><span class="hljs-selector-class">.client</span><span class="hljs-selector-class">.ChatClient</span>  - GroupChatResponseMessage&#123;from=<span class="hljs-string">'zhangsan'</span>, <span class="hljs-attribute">content</span>=<span class="hljs-string">'你们好'</span>&#125;</code></pre>

<p>接收方</p>
<pre><code class="hljs basic"><span class="hljs-symbol">48082 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - GroupChatResponseMessage&#123;from=<span class="hljs-comment">'zhangsan', content='你们好'&#125;</span></code></pre>

<p>发送方（群聊不存在）</p>
<pre><code class="hljs routeros">gsend Spring学习 你们好

25140 [nioEventLoopGroup-2-1] <span class="hljs-builtin-name">DEBUG</span> cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;<span class="hljs-attribute">success</span>=<span class="hljs-literal">false</span>, <span class="hljs-attribute">reason</span>=<span class="hljs-string">'群聊不存在'</span>&#125;</code></pre>



<h4 id="加入"><a href="#加入" class="headerlink" title="加入"></a>加入</h4><pre><code class="hljs java"><span class="hljs-meta">@ChannelHandler</span>.Sharable
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GroupJoinMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler</span>&lt;<span class="hljs-title">GroupJoinRequestMessage</span>&gt; </span>&#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext ctx, GroupJoinRequestMessage msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        GroupSession groupSession = GroupSessionFactory.getGroupSession();
        <span class="hljs-comment">// 判断该用户是否在群聊中</span>
        Set&lt;String&gt; members = groupSession.getMembers(msg.getGroupName());
        <span class="hljs-keyword">boolean</span> joinFlag = <span class="hljs-keyword">false</span>;
        <span class="hljs-comment">// 群聊存在且用户未加入，才能加入</span>
        <span class="hljs-keyword">if</span> (!members.contains(msg.getUsername()) &amp;&amp; groupSession.isCreated(msg.getGroupName())) &#123;
            joinFlag = <span class="hljs-keyword">true</span>;
        &#125;

        <span class="hljs-keyword">if</span> (joinFlag) &#123;
            <span class="hljs-comment">// 加入群聊</span>
            groupSession.joinMember(msg.getGroupName(), msg.getUsername());
            ctx.writeAndFlush(<span class="hljs-keyword">new</span> GroupJoinResponseMessage(<span class="hljs-keyword">true</span>,<span class="hljs-string">"加入"</span>+msg.getGroupName()+<span class="hljs-string">"成功"</span>));
        &#125; <span class="hljs-keyword">else</span> &#123;
            ctx.writeAndFlush(<span class="hljs-keyword">new</span> GroupJoinResponseMessage(<span class="hljs-keyword">false</span>, <span class="hljs-string">"加入失败，群聊未存在或您已加入该群聊"</span>));
        &#125;
    &#125;
&#125;</code></pre>

<pre><code class="hljs java"><span class="hljs-comment">// 该handler处理加入群聊</span>
GroupJoinMessageHandler groupJoinMessageHandler = <span class="hljs-keyword">new</span> GroupJoinMessageHandler();
ch.pipeline().addLast(groupJoinMessageHandler);</code></pre>



<p><strong>运行结果</strong></p>
<p>正常加入群聊</p>
<pre><code class="hljs basic"><span class="hljs-symbol">94921 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=true, reason=<span class="hljs-comment">'加入Netty学习成功'&#125;</span></code></pre>

<p>加入不能存在或已加入的群聊</p>
<pre><code class="hljs basic"><span class="hljs-symbol">44025 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=false, reason=<span class="hljs-comment">'加入失败，群聊未存在或您已加入该群聊'&#125;</span></code></pre>



<h4 id="退出"><a href="#退出" class="headerlink" title="退出"></a>退出</h4><pre><code class="hljs java"><span class="hljs-meta">@ChannelHandler</span>.Sharable
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GroupQuitMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler</span>&lt;<span class="hljs-title">GroupQuitRequestMessage</span>&gt; </span>&#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext ctx, GroupQuitRequestMessage msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        GroupSession groupSession = GroupSessionFactory.getGroupSession();
        String groupName = msg.getGroupName();
        Set&lt;String&gt; members = groupSession.getMembers(groupName);
        String username = msg.getUsername();
        <span class="hljs-comment">// 判断用户是否在群聊中以及群聊是否存在</span>
        <span class="hljs-keyword">boolean</span> joinFlag = <span class="hljs-keyword">false</span>;
        <span class="hljs-keyword">if</span> (groupSession.isCreated(groupName) &amp;&amp; members.contains(username)) &#123;
            <span class="hljs-comment">// 可以退出</span>
            joinFlag = <span class="hljs-keyword">true</span>;
        &#125;

        <span class="hljs-keyword">if</span> (joinFlag) &#123;
            <span class="hljs-comment">// 退出成功</span>
            groupSession.removeMember(groupName, username);
            ctx.writeAndFlush(<span class="hljs-keyword">new</span> GroupQuitResponseMessage(<span class="hljs-keyword">true</span>, <span class="hljs-string">"退出"</span>+groupName+<span class="hljs-string">"成功"</span>));
        &#125; <span class="hljs-keyword">else</span> &#123;
            <span class="hljs-comment">// 退出失败</span>
            ctx.writeAndFlush(<span class="hljs-keyword">new</span> GroupQuitResponseMessage(<span class="hljs-keyword">false</span>, <span class="hljs-string">"群聊不存在或您未加入该群，退出"</span>+groupName+<span class="hljs-string">"失败"</span>));
        &#125;
    &#125;
&#125;</code></pre>

<pre><code class="hljs java"><span class="hljs-comment">// 该handler处理退出群聊</span>
GroupQuitMessageHandler groupQuitMessageHandler = <span class="hljs-keyword">new</span> GroupQuitMessageHandler();
ch.pipeline().addLast(groupQuitMessageHandler);</code></pre>

<p><strong>运行结果</strong></p>
<p>正常退出</p>
<pre><code class="hljs basic"><span class="hljs-symbol">32282 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=true, reason=<span class="hljs-comment">'退出Netty学习成功'&#125;</span></code></pre>



<p>退出不存在或未加入的群聊</p>
<pre><code class="hljs basic"><span class="hljs-symbol">67404 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - AbstractResponseMessage&#123;success=false, reason=<span class="hljs-comment">'群聊不存在或您未加入该群，退出Netty失败'&#125;</span></code></pre>



<h4 id="查看成员"><a href="#查看成员" class="headerlink" title="查看成员"></a>查看成员</h4><pre><code class="hljs java"><span class="hljs-meta">@ChannelHandler</span>.Sharable
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GroupMembersMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler</span>&lt;<span class="hljs-title">GroupMembersRequestMessage</span>&gt; </span>&#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext ctx, GroupMembersRequestMessage msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        ctx.writeAndFlush(<span class="hljs-keyword">new</span> GroupMembersResponseMessage(GroupSessionFactory.getGroupSession().getMembers(msg.getGroupName())));
    &#125;
&#125;</code></pre>

<pre><code class="hljs JAVA"><span class="hljs-comment">// 该handler处理查看成员</span>
GroupMembersMessageHandler groupMembersMessageHandler = <span class="hljs-keyword">new</span> GroupMembersMessageHandler();
ch.pipeline().addLast(groupMembersMessageHandler);</code></pre>

<p><strong>运行结果</strong></p>
<pre><code class="hljs basic"><span class="hljs-symbol">46557 </span>[nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.client.ChatClient  - GroupMembersResponseMessage&#123;members=[zhangsan, Nyima]&#125;</code></pre>



<h3 id="退出聊天室"><a href="#退出聊天室" class="headerlink" title="退出聊天室"></a>退出聊天室</h3><pre><code class="hljs java"><span class="hljs-meta">@ChannelHandler</span>.Sharable
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">QuitHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ChannelInboundHandlerAdapter</span> </span>&#123;
    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 断开连接时触发 Inactive事件</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelInactive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        <span class="hljs-comment">// 解绑</span>
        SessionFactory.getSession().unbind(ctx.channel());
    &#125;

    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 异常退出，需要解绑</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">exceptionCaught</span><span class="hljs-params">(ChannelHandlerContext ctx, Throwable cause)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        <span class="hljs-comment">// 解绑</span>
        SessionFactory.getSession().unbind(ctx.channel());
    &#125;
&#125;</code></pre>

<pre><code class="hljs java"><span class="hljs-comment">// 该handler处理退出聊天室</span>
ch.pipeline().addLast(quitHandler);
GroupMembersMessageHandler groupMembersMessageHandler = <span class="hljs-keyword">new</span> GroupMembersMessageHandler();</code></pre>



<p><strong>退出时，客户端会关闭channel并返回</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">case</span> <span class="hljs-string">"quit"</span>:
	<span class="hljs-comment">// 关闭channel并返回</span>
    ctx.channel().close();
    <span class="hljs-keyword">return</span>;</code></pre>



<h3 id="空闲检测"><a href="#空闲检测" class="headerlink" title="空闲检测"></a>空闲检测</h3><h4 id="连接假死"><a href="#连接假死" class="headerlink" title="连接假死"></a>连接假死</h4><p><strong>原因</strong></p>
<ul>
<li>网络设备出现故障，例如网卡，机房等，底层的 TCP 连接已经断开了，<strong>但应用程序没有感知到</strong>，仍然占用着资源</li>
<li>公网网络不稳定，出现丢包。如果连续出现丢包，这时现象就是客户端数据发不出去，服务端也一直收不到数据，会白白地消耗资源</li>
<li>应用程序线程阻塞，无法进行数据读写</li>
</ul>
<p><strong>问题</strong></p>
<ul>
<li>假死的连接占用的资源不能自动释放</li>
<li>向假死的连接发送数据，得到的反馈是发送超时</li>
</ul>
<h4 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h4><p>可以添加<code>IdleStateHandler</code>对空闲时间进行检测，通过构造函数可以传入三个参数</p>
<ul>
<li>readerIdleTimeSeconds 读空闲经过的秒数</li>
<li>writerIdleTimeSeconds 写空闲经过的秒数</li>
<li>allIdleTimeSeconds 读和写空闲经过的秒数</li>
</ul>
<p>当指定时间内未发生读或写事件时，<strong>会触发特定事件</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210428132848.png" srcset="/img/loading.gif" alt=""></p>
<ul>
<li>读空闲会触发<code>READER_IDLE</code></li>
<li>写空闲会触发<code>WRITE_IDLE</code></li>
<li>读和写空闲会触发<code>ALL_IDEL</code></li>
</ul>
<p>想要处理这些事件，<strong>需要自定义事件处理函数</strong></p>
<p><strong>服务器端代码</strong></p>
<pre><code class="hljs java"><span class="hljs-comment">// 用于空闲连接的检测，5s内未读到数据，会触发READ_IDLE事件</span>
ch.pipeline().addLast(<span class="hljs-keyword">new</span> IdleStateHandler(<span class="hljs-number">5</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>));
<span class="hljs-comment">// 添加双向处理器，负责处理READER_IDLE事件</span>
ch.pipeline().addLast(<span class="hljs-keyword">new</span> ChannelDuplexHandler() &#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">userEventTriggered</span><span class="hljs-params">(ChannelHandlerContext ctx, Object evt)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        <span class="hljs-comment">// 获得事件</span>
        IdleStateEvent event = (IdleStateEvent) evt;
        <span class="hljs-keyword">if</span> (event.state() == IdleState.READER_IDLE) &#123;
            <span class="hljs-comment">// 断开连接</span>
            ctx.channel().close();
        &#125;
    &#125;
&#125;);</code></pre>

<ul>
<li>使用<code>IdleStateHandler</code>进行空闲检测</li>
<li>使用双向处理器<code>ChannelDuplexHandler</code>对入站与出站事件进行处理<ul>
<li><code>IdleStateHandler</code>中的事件为特殊事件，需要实现<code>ChannelDuplexHandler</code>的<code>userEventTriggered</code>方法，判断事件类型并自定义处理方式，来对事件进行处理</li>
</ul>
</li>
</ul>
<p>为<strong>避免因非网络等原因引发的READ_IDLE事件</strong>，比如网络情况良好，只是用户本身没有输入数据，这时发生READ_IDLE事件，<strong>直接让服务器断开连接是不可取的</strong></p>
<p>为避免此类情况，需要在<strong>客户端向服务器发送心跳包</strong>，发送频率要<strong>小于</strong>服务器设置的<code>IdleTimeSeconds</code>，一般设置为其值的一半</p>
<p><strong>客户端代码</strong></p>
<pre><code class="hljs java"><span class="hljs-comment">// 发送心跳包，让服务器知道客户端在线</span>
<span class="hljs-comment">// 3s未发生WRITER_IDLE，就像服务器发送心跳包</span>
<span class="hljs-comment">// 该值为服务器端设置的READER_IDLE触发时间的一半左右</span>
ch.pipeline().addLast(<span class="hljs-keyword">new</span> IdleStateHandler(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>, <span class="hljs-number">0</span>));
ch.pipeline().addLast(<span class="hljs-keyword">new</span> ChannelDuplexHandler() &#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">userEventTriggered</span><span class="hljs-params">(ChannelHandlerContext ctx, Object evt)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        IdleStateEvent event = (IdleStateEvent) evt;
        <span class="hljs-keyword">if</span> (event.state() == IdleState.WRITER_IDLE) &#123;
            <span class="hljs-comment">// 发送心跳包</span>
            ctx.writeAndFlush(<span class="hljs-keyword">new</span> PingMessage());
        &#125;
    &#125;
&#125;);</code></pre>



<h1 id="五、优化"><a href="#五、优化" class="headerlink" title="五、优化"></a>五、优化</h1><h2 id="1、拓展序列化算法"><a href="#1、拓展序列化算法" class="headerlink" title="1、拓展序列化算法"></a>1、拓展序列化算法</h2><h3 id="序列化接口"><a href="#序列化接口" class="headerlink" title="序列化接口"></a>序列化接口</h3><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">Serializer</span> </span>&#123;
    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 序列化</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> object 被序列化的对象</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> &lt;T&gt; 被序列化对象类型</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 序列化后的字节数组</span>
<span class="hljs-comment">     */</span>
    &lt;T&gt; <span class="hljs-keyword">byte</span>[] serialize(T object);

    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 反序列化</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> clazz 反序列化的目标类的Class对象</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> bytes 被反序列化的字节数组</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@param</span> &lt;T&gt; 反序列化目标类</span>
<span class="hljs-comment">     * <span class="hljs-doctag">@return</span> 反序列化后的对象</span>
<span class="hljs-comment">     */</span>
    &lt;T&gt; <span class="hljs-function">T <span class="hljs-title">deserialize</span><span class="hljs-params">(Class&lt;T&gt; clazz, <span class="hljs-keyword">byte</span>[] bytes)</span></span>;
&#125;</code></pre>



<h3 id="枚举实现类"><a href="#枚举实现类" class="headerlink" title="枚举实现类"></a>枚举实现类</h3><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> SerializerAlgorithm implements Serializer &#123;
    <span class="hljs-comment">// Java的序列化和反序列化</span>
    Java &#123;
        <span class="hljs-meta">@Override</span>
        <span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-keyword">byte</span>[] serialize(T object) &#123;
            <span class="hljs-comment">// 序列化后的字节数组</span>
            <span class="hljs-keyword">byte</span>[] bytes = <span class="hljs-keyword">null</span>;
            <span class="hljs-keyword">try</span> (ByteArrayOutputStream bos = <span class="hljs-keyword">new</span> ByteArrayOutputStream();
                 ObjectOutputStream oos = <span class="hljs-keyword">new</span> ObjectOutputStream(bos)) &#123;
                oos.writeObject(object);
                bytes = bos.toByteArray();
            &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;
                e.printStackTrace();
            &#125;
            <span class="hljs-keyword">return</span> bytes;
        &#125;

        <span class="hljs-meta">@Override</span>
        <span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-function">T <span class="hljs-title">deserialize</span><span class="hljs-params">(Class&lt;T&gt; clazz, <span class="hljs-keyword">byte</span>[] bytes)</span> </span>&#123;
            T target = <span class="hljs-keyword">null</span>;
            System.out.println(Arrays.toString(bytes));
            <span class="hljs-keyword">try</span> (ByteArrayInputStream bis = <span class="hljs-keyword">new</span> ByteArrayInputStream(bytes);
                 ObjectInputStream ois = <span class="hljs-keyword">new</span> ObjectInputStream(bis)) &#123;
                target = (T) ois.readObject();
            &#125; <span class="hljs-keyword">catch</span> (IOException | ClassNotFoundException e) &#123;
                e.printStackTrace();
            &#125;
            <span class="hljs-comment">// 返回反序列化后的对象</span>
            <span class="hljs-keyword">return</span> target;
        &#125;
    &#125;
    
     <span class="hljs-comment">// Json的序列化和反序列化</span>
    Json &#123;
        <span class="hljs-meta">@Override</span>
        <span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-keyword">byte</span>[] serialize(T object) &#123;
            String s = <span class="hljs-keyword">new</span> Gson().toJson(object);
            System.out.println(s);
            <span class="hljs-comment">// 指定字符集，获得字节数组</span>
            <span class="hljs-keyword">return</span> s.getBytes(StandardCharsets.UTF_8);
        &#125;

        <span class="hljs-meta">@Override</span>
        <span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-function">T <span class="hljs-title">deserialize</span><span class="hljs-params">(Class&lt;T&gt; clazz, <span class="hljs-keyword">byte</span>[] bytes)</span> </span>&#123;
            String s = <span class="hljs-keyword">new</span> String(bytes, StandardCharsets.UTF_8);
            System.out.println(s);
            <span class="hljs-comment">// 此处的clazz为具体类型的Class对象，而不是父类Message的</span>
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Gson().fromJson(s, clazz);
        &#125;
    &#125;
&#125;</code></pre>



<h3 id="修改原编解码器"><a href="#修改原编解码器" class="headerlink" title="修改原编解码器"></a>修改原编解码器</h3><p><strong>编码</strong></p>
<pre><code class="hljs java"><span class="hljs-comment">// 获得序列化后的msg</span>
<span class="hljs-comment">// 使用指定的序列化方式</span>
SerializerAlgorithm[] values = SerializerAlgorithm.values();
<span class="hljs-comment">// 获得序列化后的对象</span>
<span class="hljs-keyword">byte</span>[] bytes = values[out.getByte(<span class="hljs-number">5</span>)-<span class="hljs-number">1</span>].serialize(msg);</code></pre>

<p><strong>解码</strong></p>
<pre><code class="hljs java"><span class="hljs-comment">// 获得反序列化方式</span>
SerializerAlgorithm[] values = SerializerAlgorithm.values();
<span class="hljs-comment">// 通过指定方式进行反序列化</span>
<span class="hljs-comment">// 需要通过Message的方法获得具体的消息类型</span>
Message message = values[seqType-<span class="hljs-number">1</span>].deserialize(Message.getMessageClass(messageType), bytes);</code></pre>



<h2 id="2、参数调优"><a href="#2、参数调优" class="headerlink" title="2、参数调优"></a>2、参数调优</h2><h3 id="CONNECT-TIMEOUT-MILLIS"><a href="#CONNECT-TIMEOUT-MILLIS" class="headerlink" title="CONNECT_TIMEOUT_MILLIS"></a>CONNECT_TIMEOUT_MILLIS</h3><ul>
<li>属于 <strong>SocketChannal</strong> 的参数</li>
<li>用在<strong>客户端建立连接</strong>时，如果在指定毫秒内无法连接，会抛出 timeout 异常</li>
<li><strong>注意</strong>：Netty 中不要用成了SO_TIMEOUT 主要用在阻塞 IO，而 Netty 是非阻塞 IO</li>
</ul>
<h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a><strong>使用</strong></h4><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestParam</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-comment">// SocketChannel 5s内未建立连接就抛出异常</span>
        <span class="hljs-keyword">new</span> Bootstrap().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, <span class="hljs-number">5000</span>);
        
        <span class="hljs-comment">// ServerSocketChannel 5s内未建立连接就抛出异常</span>
        <span class="hljs-keyword">new</span> ServerBootstrap().option(ChannelOption.CONNECT_TIMEOUT_MILLIS,<span class="hljs-number">5000</span>);
        <span class="hljs-comment">// SocketChannel 5s内未建立连接就抛出异常</span>
        <span class="hljs-keyword">new</span> ServerBootstrap().childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, <span class="hljs-number">5000</span>);
    &#125;
&#125;</code></pre>

<ul>
<li>客户端通过 <code>Bootstrap.option</code> 函数来配置参数，<strong>配置参数作用于 SocketChannel</strong> </li>
<li>服务器通过 <code>ServerBootstrap</code>来配置参数，但是对于不同的 Channel 需要选择不同的方法<ul>
<li>通过 <code>option</code> 来配置 <strong>ServerSocketChannel</strong> 上的参数</li>
<li>通过 <code>childOption</code> 来配置 <strong>SocketChannel</strong> 上的参数</li>
</ul>
</li>
</ul>
<h4 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h4><p>客户端中连接服务器的线程是 NIO 线程，抛出异常的是主线程。这是如何做到超时判断以及线程通信的呢？</p>
<p><code>AbstractNioChannel.AbstractNioUnsafe.connect</code>方法中</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title">connect</span><span class="hljs-params">(</span></span>
<span class="hljs-function"><span class="hljs-params">                <span class="hljs-keyword">final</span> SocketAddress remoteAddress, <span class="hljs-keyword">final</span> SocketAddress localAddress, <span class="hljs-keyword">final</span> ChannelPromise promise)</span> </span>&#123;
    
    ...
        
    <span class="hljs-comment">// Schedule connect timeout.</span>
    <span class="hljs-comment">// 设置超时时间，通过option方法传入的CONNECT_TIMEOUT_MILLIS参数进行设置</span>
    <span class="hljs-keyword">int</span> connectTimeoutMillis = config().getConnectTimeoutMillis();
    <span class="hljs-comment">// 如果超时时间大于0</span>
    <span class="hljs-keyword">if</span> (connectTimeoutMillis &gt; <span class="hljs-number">0</span>) &#123;
        <span class="hljs-comment">// 创建一个定时任务，延时connectTimeoutMillis（设置的超时时间时间）后执行</span>
        <span class="hljs-comment">// schedule(Runnable command, long delay, TimeUnit unit)</span>
        connectTimeoutFuture = eventLoop().schedule(<span class="hljs-keyword">new</span> Runnable() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
                <span class="hljs-comment">// 判断是否建立连接，Promise进行NIO线程与主线程之间的通信</span>
                <span class="hljs-comment">// 如果超时，则通过tryFailure方法将异常放入Promise中</span>
                <span class="hljs-comment">// 在主线程中抛出</span>
                ChannelPromise connectPromise = AbstractNioChannel.<span class="hljs-keyword">this</span>.connectPromise;
                ConnectTimeoutException cause = <span class="hljs-keyword">new</span> ConnectTimeoutException(<span class="hljs-string">"connection timed out: "</span> + remoteAddress);
                <span class="hljs-keyword">if</span> (connectPromise != <span class="hljs-keyword">null</span> &amp;&amp; connectPromise.tryFailure(cause)) &#123;
                    close(voidPromise());
                &#125;
            &#125;
        &#125;, connectTimeoutMillis, TimeUnit.MILLISECONDS);
    &#125;
    
   	...
        
&#125;</code></pre>

<p>超时的判断<strong>主要是通过 Eventloop 的 schedule 方法和 Promise 共同实现的</strong></p>
<ul>
<li>schedule 设置了一个定时任务，延迟<code>connectTimeoutMillis</code>秒后执行该方法</li>
<li>如果指定时间内没有建立连接，则会执行其中的任务<ul>
<li>任务负责创建 <code>ConnectTimeoutException</code> 异常，并将异常通过 Pormise 传给主线程并抛出</li>
</ul>
</li>
</ul>
<h3 id="SO-BACKLOG"><a href="#SO-BACKLOG" class="headerlink" title="SO_BACKLOG"></a>SO_BACKLOG</h3><p>该参数是 <strong>ServerSocketChannel</strong> 的参数</p>
<h4 id="三次握手与连接队列"><a href="#三次握手与连接队列" class="headerlink" title="三次握手与连接队列"></a>三次握手与连接队列</h4><p>第一次握手时，因为客户端与服务器之间的连接还未完全建立，连接会被放入<strong>半连接队列</strong>中</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210429211913.png" srcset="/img/loading.gif" alt=""></p>
<p>当完成三次握手以后，连接会被放入<strong>全连接队列中</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210429211952.png" srcset="/img/loading.gif" alt=""></p>
<p>服务器处理Accept事件是在TCP三次握手，也就是建立连接之后。服务器会从全连接队列中获取连接并进行处理</p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210430094836.png" srcset="/img/loading.gif" alt=""></p>
<p>在 linux 2.2 之前，backlog 大小包括了两个队列的大小，<strong>在 linux 2.2 之后，分别用下面两个参数来控制</strong></p>
<ul>
<li>半连接队列 - sync queue<ul>
<li>大小通过 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定，在 <code>syncookies</code> 启用的情况下，逻辑上没有最大值限制，这个设置便被忽略</li>
</ul>
</li>
<li><strong>全连接队列 - accept queue</strong><ul>
<li>其大小通过 /proc/sys/net/core/somaxconn 指定，在使用 listen 函数时，<strong>内核会根据传入的 backlog 参数与系统参数，取二者的较小值</strong></li>
<li>如果 accpet queue 队列满了，server 将发送一个拒绝连接的错误信息到 client</li>
</ul>
</li>
</ul>
<h4 id="作用"><a href="#作用" class="headerlink" title="作用"></a>作用</h4><p>在Netty中，<code>SO_BACKLOG</code>主要用于设置全连接队列的大小。<strong>当处理Accept的速率小于连接建立的速率时，全连接队列中堆积的连接数大于<code>SO_BACKLOG</code>设置的值是，便会抛出异常</strong></p>
<p><strong>设置方式如下</strong></p>
<pre><code class="hljs java"><span class="hljs-comment">// 设置全连接队列，大小为2</span>
<span class="hljs-keyword">new</span> ServerBootstrap().option(ChannelOption.SO_BACKLOG, <span class="hljs-number">2</span>);</code></pre>



<h4 id="默认值"><a href="#默认值" class="headerlink" title="默认值"></a>默认值</h4><p>backlog参数在<code>NioSocketChannel.doBind</code>方法被使用</p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doBind</span><span class="hljs-params">(SocketAddress localAddress)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
    <span class="hljs-keyword">if</span> (PlatformDependent.javaVersion() &gt;= <span class="hljs-number">7</span>) &#123;
        javaChannel().bind(localAddress, config.getBacklog());
    &#125; <span class="hljs-keyword">else</span> &#123;
        javaChannel().socket().bind(localAddress, config.getBacklog());
    &#125;
&#125;</code></pre>

<p>其中backlog被保存在了<code>DefaultServerSocketChannelConfig</code>配置类中</p>
<pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">volatile</span> <span class="hljs-keyword">int</span> backlog = NetUtil.SOMAXCONN;</code></pre>

<p>具体的赋值操作如下</p>
<pre><code class="hljs java">SOMAXCONN = AccessController.doPrivileged(<span class="hljs-keyword">new</span> PrivilegedAction&lt;Integer&gt;() &#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Integer <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
        <span class="hljs-comment">// Determine the default somaxconn (server socket backlog) value of the platform.</span>
        <span class="hljs-comment">// The known defaults:</span>
        <span class="hljs-comment">// - Windows NT Server 4.0+: 200</span>
        <span class="hljs-comment">// - Linux and Mac OS X: 128</span>
        <span class="hljs-keyword">int</span> somaxconn = PlatformDependent.isWindows() ? <span class="hljs-number">200</span> : <span class="hljs-number">128</span>;
        File file = <span class="hljs-keyword">new</span> File(<span class="hljs-string">"/proc/sys/net/core/somaxconn"</span>);
        BufferedReader in = <span class="hljs-keyword">null</span>;
        <span class="hljs-keyword">try</span> &#123;
            <span class="hljs-comment">// file.exists() may throw a SecurityException if a SecurityManager is used, so execute it in the</span>
            <span class="hljs-comment">// try / catch block.</span>
            <span class="hljs-comment">// See https://github.com/netty/netty/issues/4936</span>
            <span class="hljs-keyword">if</span> (file.exists()) &#123;
                in = <span class="hljs-keyword">new</span> BufferedReader(<span class="hljs-keyword">new</span> FileReader(file));
                <span class="hljs-comment">// 将somaxconn设置为Linux配置文件中设置的值</span>
                somaxconn = Integer.parseInt(in.readLine());
                <span class="hljs-keyword">if</span> (logger.isDebugEnabled()) &#123;
                    logger.debug(<span class="hljs-string">"&#123;&#125;: &#123;&#125;"</span>, file, somaxconn);
                &#125;
            &#125; <span class="hljs-keyword">else</span> &#123;
                ...
            &#125;
            ...
        &#125;  
        <span class="hljs-comment">// 返回backlog的值</span>
        <span class="hljs-keyword">return</span> somaxconn;
    &#125;
&#125;</code></pre>

<ul>
<li>backlog的值会根据操作系统的不同，来<strong>选择不同的默认值</strong><ul>
<li>Windows 200</li>
<li>Linux/Mac OS 128</li>
</ul>
</li>
<li><strong>如果配置文件<code>/proc/sys/net/core/somaxconn</code>存在</strong>，会读取配置文件中的值，并将backlog的值设置为配置文件中指定的</li>
</ul>
<h3 id="TCP-NODELAY"><a href="#TCP-NODELAY" class="headerlink" title="TCP_NODELAY"></a>TCP_NODELAY</h3><ul>
<li>属于 <strong>SocketChannal</strong> 参数</li>
<li>因为 Nagle 算法，数据包会堆积到一定的数量后一起发送，这就<strong>可能导致数据的发送存在一定的延时</strong></li>
<li><strong>该参数默认为false</strong>，如果不希望的发送被延时，则需要将该值设置为true</li>
</ul>
<h3 id="SO-SNDBUF-amp-SO-RCVBUF"><a href="#SO-SNDBUF-amp-SO-RCVBUF" class="headerlink" title="SO_SNDBUF &amp; SO_RCVBUF"></a>SO_SNDBUF &amp; SO_RCVBUF</h3><ul>
<li>SO_SNDBUF 属于 <strong>SocketChannal</strong> 参数</li>
<li>SO_RCVBUF <strong>既可用于 SocketChannal 参数，也可以用于 ServerSocketChannal 参数</strong>（建议设置到 ServerSocketChannal 上）</li>
<li>该参数用于<strong>指定接收方与发送方的滑动窗口大小</strong></li>
</ul>
<h3 id="ALLOCATOR"><a href="#ALLOCATOR" class="headerlink" title="ALLOCATOR"></a>ALLOCATOR</h3><ul>
<li>属于 <strong>SocketChannal</strong> 参数</li>
<li>用来配置 ByteBuf 是池化还是非池化，是直接内存还是堆内存</li>
</ul>
<h4 id="使用-1"><a href="#使用-1" class="headerlink" title="使用"></a>使用</h4><pre><code class="hljs java"><span class="hljs-comment">// 选择ALLOCATOR参数，设置SocketChannel中分配的ByteBuf类型</span>
<span class="hljs-comment">// 第二个参数需要传入一个ByteBufAllocator，用于指定生成的 ByteBuf 的类型</span>
<span class="hljs-keyword">new</span> ServerBootstrap().childOption(ChannelOption.ALLOCATOR, <span class="hljs-keyword">new</span> PooledByteBufAllocator());</code></pre>



<p><strong>ByteBufAllocator类型</strong></p>
<ul>
<li><p>池化并使用直接内存</p>
<pre><code class="hljs java"><span class="hljs-comment">// true表示使用直接内存</span>
<span class="hljs-keyword">new</span> PooledByteBufAllocator(<span class="hljs-keyword">true</span>);</code></pre>
</li>
<li><p>池化并使用堆内存</p>
<pre><code class="hljs java"><span class="hljs-comment">// false表示使用堆内存</span>
<span class="hljs-keyword">new</span> PooledByteBufAllocator(<span class="hljs-keyword">false</span>);</code></pre>
</li>
<li><p>非池化并使用直接内存</p>
<pre><code class="hljs java"><span class="hljs-comment">// ture表示使用直接内存</span>
<span class="hljs-keyword">new</span> UnpooledByteBufAllocator(<span class="hljs-keyword">true</span>);</code></pre>
</li>
<li><p>非池化并使用堆内存</p>
<pre><code class="hljs java"><span class="hljs-comment">// false表示使用堆内存</span>
<span class="hljs-keyword">new</span> UnpooledByteBufAllocator(<span class="hljs-keyword">false</span>);</code></pre>



</li>
</ul>
<h3 id="RCVBUF-ALLOCATOR"><a href="#RCVBUF-ALLOCATOR" class="headerlink" title="RCVBUF_ALLOCATOR"></a>RCVBUF_ALLOCATOR</h3><ul>
<li>属于 <strong>SocketChannal</strong> 参数</li>
<li><strong>控制 Netty 接收缓冲区大小</strong></li>
<li>负责入站数据的分配，决定入站缓冲区的大小（并可动态调整），<strong>统一采用 direct 直接内存</strong>，具体池化还是非池化由 allocator 决定</li>
</ul>
<h2 id="3、RPC框架"><a href="#3、RPC框架" class="headerlink" title="3、RPC框架"></a>3、RPC框架</h2><h3 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h3><p>在聊天室代码的基础上进行一定的改进</p>
<p><strong>Message</strong>中添加如下代码</p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Message</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Serializable</span> </span>&#123;

    ...

    <span class="hljs-comment">// 添加RPC消息类型</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> RPC_MESSAGE_TYPE_REQUEST = <span class="hljs-number">101</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span>  RPC_MESSAGE_TYPE_RESPONSE = <span class="hljs-number">102</span>;

    <span class="hljs-keyword">static</span> &#123;
        <span class="hljs-comment">// 将消息类型放入消息类对象Map中</span>
        messageClasses.put(RPC_MESSAGE_TYPE_REQUEST, RpcRequestMessage<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
        messageClasses.put(RPC_MESSAGE_TYPE_RESPONSE, RpcResponseMessage<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
    &#125;

&#125;</code></pre>



<p><strong>RPC请求消息</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RpcRequestMessage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Message</span> </span>&#123;
    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 调用的接口全限定名，服务端根据它找到实现</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-keyword">private</span> String interfaceName;
    
    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 调用接口中的方法名</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-keyword">private</span> String methodName;
    
    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 方法返回类型</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-keyword">private</span> Class&lt;?&gt; returnType;
    
    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 方法参数类型数组</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-keyword">private</span> Class[] parameterTypes;
    
    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 方法参数值数组</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-keyword">private</span> Object[] parameterValue;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">RpcRequestMessage</span><span class="hljs-params">(<span class="hljs-keyword">int</span> sequenceId, String interfaceName, String methodName, Class&lt;?&gt; returnType, Class[] parameterTypes, Object[] parameterValue)</span> </span>&#123;
        <span class="hljs-keyword">super</span>.setSequenceId(sequenceId);
        <span class="hljs-keyword">this</span>.interfaceName = interfaceName;
        <span class="hljs-keyword">this</span>.methodName = methodName;
        <span class="hljs-keyword">this</span>.returnType = returnType;
        <span class="hljs-keyword">this</span>.parameterTypes = parameterTypes;
        <span class="hljs-keyword">this</span>.parameterValue = parameterValue;
    &#125;

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getMessageType</span><span class="hljs-params">()</span> </span>&#123;
        <span class="hljs-keyword">return</span> RPC_MESSAGE_TYPE_REQUEST;
    &#125;
    
     <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getInterfaceName</span><span class="hljs-params">()</span> </span>&#123;
        <span class="hljs-keyword">return</span> interfaceName;
    &#125;

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getMethodName</span><span class="hljs-params">()</span> </span>&#123;
        <span class="hljs-keyword">return</span> methodName;
    &#125;

    <span class="hljs-keyword">public</span> Class&lt;?&gt; getReturnType() &#123;
        <span class="hljs-keyword">return</span> returnType;
    &#125;

    <span class="hljs-keyword">public</span> Class[] getParameterTypes() &#123;
        <span class="hljs-keyword">return</span> parameterTypes;
    &#125;

    <span class="hljs-keyword">public</span> Object[] getParameterValue() &#123;
        <span class="hljs-keyword">return</span> parameterValue;
    &#125;
    
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">toString</span><span class="hljs-params">()</span> </span>&#123;
        <span class="hljs-keyword">return</span> <span class="hljs-string">"RpcRequestMessage&#123;"</span> +
                <span class="hljs-string">"interfaceName='"</span> + interfaceName + <span class="hljs-string">'\''</span> +
                <span class="hljs-string">", methodName='"</span> + methodName + <span class="hljs-string">'\''</span> +
                <span class="hljs-string">", returnType="</span> + returnType +
                <span class="hljs-string">", parameterTypes="</span> + Arrays.toString(parameterTypes) +
                <span class="hljs-string">", parameterValue="</span> + Arrays.toString(parameterValue) +
                <span class="hljs-string">'&#125;'</span>;
    &#125;
&#125;</code></pre>

<p>想要远程调用一个方法，必须知道以<strong>下五个信息</strong></p>
<ul>
<li>方法所在的全限定类名</li>
<li>方法名</li>
<li>方法返回值类型</li>
<li>方法参数类型</li>
<li>方法参数值</li>
</ul>
<p><strong>RPC响应消息</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RpcResponseMessage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Message</span> </span>&#123;
    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 返回值</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-keyword">private</span> Object returnValue;
    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 异常值</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-keyword">private</span> Exception exceptionValue;

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getMessageType</span><span class="hljs-params">()</span> </span>&#123;
        <span class="hljs-keyword">return</span> RPC_MESSAGE_TYPE_RESPONSE;
    &#125;
    
    
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setReturnValue</span><span class="hljs-params">(Object returnValue)</span> </span>&#123;
        <span class="hljs-keyword">this</span>.returnValue = returnValue;
    &#125;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setExceptionValue</span><span class="hljs-params">(Exception exceptionValue)</span> </span>&#123;
        <span class="hljs-keyword">this</span>.exceptionValue = exceptionValue;
    &#125;
    
     <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">getReturnValue</span><span class="hljs-params">()</span> </span>&#123;
        <span class="hljs-keyword">return</span> returnValue;
    &#125;

    <span class="hljs-function"><span class="hljs-keyword">public</span> Exception <span class="hljs-title">getExceptionValue</span><span class="hljs-params">()</span> </span>&#123;
        <span class="hljs-keyword">return</span> exceptionValue;
    &#125;
    
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">toString</span><span class="hljs-params">()</span> </span>&#123;
        <span class="hljs-keyword">return</span> <span class="hljs-string">"RpcResponseMessage&#123;"</span> +
                <span class="hljs-string">"returnValue="</span> + returnValue +
                <span class="hljs-string">", exceptionValue="</span> + exceptionValue +
                <span class="hljs-string">'&#125;'</span>;
    &#125;
&#125;</code></pre>

<p>响应消息中只需要获取<strong>返回结果和异常值</strong></p>
<p><strong>服务器</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RPCServer</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        NioEventLoopGroup boss = <span class="hljs-keyword">new</span> NioEventLoopGroup();
        NioEventLoopGroup worker = <span class="hljs-keyword">new</span> NioEventLoopGroup();
        LoggingHandler loggingHandler = <span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG);
        MessageSharableCodec messageSharableCodec = <span class="hljs-keyword">new</span> MessageSharableCodec();

        <span class="hljs-comment">// PRC 请求消息处理器</span>
        RpcRequestMessageHandler rpcRequestMessageHandler = <span class="hljs-keyword">new</span> RpcRequestMessageHandler();
        <span class="hljs-keyword">try</span> &#123;
            ServerBootstrap serverBootstrap = <span class="hljs-keyword">new</span> ServerBootstrap();
            serverBootstrap.channel(NioServerSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
            serverBootstrap.group(boss, worker);
            serverBootstrap.childHandler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123;
                <span class="hljs-meta">@Override</span>
                <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                    ch.pipeline().addLast(<span class="hljs-keyword">new</span> ProtocolFrameDecoder());
                    ch.pipeline().addLast(loggingHandler);
                    ch.pipeline().addLast(messageSharableCodec);
                    ch.pipeline().addLast(rpcRequestMessageHandler);
                &#125;
            &#125;);
            Channel channel = serverBootstrap.bind(<span class="hljs-number">8080</span>).sync().channel();
            channel.closeFuture().sync();
        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;
            e.printStackTrace();
        &#125; <span class="hljs-keyword">finally</span> &#123;
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        &#125;
    &#125;
&#125;</code></pre>

<p>服务器中添加了<strong>处理RPCRequest消息的handler</strong></p>
<p><strong>客户端</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RPCClient</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        NioEventLoopGroup group = <span class="hljs-keyword">new</span> NioEventLoopGroup();
        LoggingHandler loggingHandler = <span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG);
        MessageSharableCodec messageSharableCodec = <span class="hljs-keyword">new</span> MessageSharableCodec();

        <span class="hljs-comment">// PRC 请求消息处理器</span>
        RpcResponseMessageHandler rpcResponseMessageHandler = <span class="hljs-keyword">new</span> RpcResponseMessageHandler();
        <span class="hljs-keyword">try</span> &#123;
            Bootstrap bootstrap = <span class="hljs-keyword">new</span> Bootstrap();
            bootstrap.channel(NioSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
            bootstrap.group(group);
            bootstrap.handler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123;
                <span class="hljs-meta">@Override</span>
                <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                    ch.pipeline().addLast(<span class="hljs-keyword">new</span> ProtocolFrameDecoder());
                    ch.pipeline().addLast(loggingHandler);
                    ch.pipeline().addLast(messageSharableCodec);
                    ch.pipeline().addLast(rpcResponseMessageHandler);
                &#125;
            &#125;);
            Channel channel = bootstrap.connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">"localhost"</span>, <span class="hljs-number">8080</span>)).sync().channel();
            channel.closeFuture().sync();
        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;
            e.printStackTrace();
        &#125; <span class="hljs-keyword">finally</span> &#123;
            group.shutdownGracefully();
        &#125;
    &#125;
&#125;</code></pre>



<p>通过接口Class获取实例对象的<strong>Factory</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ServicesFactory</span> </span>&#123;
    <span class="hljs-keyword">static</span> HashMap&lt;Class&lt;?&gt;, Object&gt; map = <span class="hljs-keyword">new</span> HashMap&lt;&gt;(<span class="hljs-number">16</span>);

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title">getInstance</span><span class="hljs-params">(Class&lt;?&gt; interfaceClass)</span> <span class="hljs-keyword">throws</span> ClassNotFoundException, IllegalAccessException, InstantiationException </span>&#123;
        <span class="hljs-comment">// 根据Class创建实例</span>
        <span class="hljs-keyword">try</span> &#123;
            Class&lt;?&gt; clazz = Class.forName(<span class="hljs-string">"cn.nyimac.study.day8.server.service.HelloService"</span>);
            Object instance = Class.forName(<span class="hljs-string">"cn.nyimac.study.day8.server.service.HelloServiceImpl"</span>).newInstance();
           
            <span class="hljs-comment">// 放入 InterfaceClass -&gt; InstanceObject 的映射</span>
            map.put(clazz, instance);
        &#125; <span class="hljs-keyword">catch</span> (ClassNotFoundException | InstantiationException | IllegalAccessException e) &#123;
            e.printStackTrace();
        &#125;  
        <span class="hljs-keyword">return</span> map.get(interfaceClass);
    &#125;
&#125;</code></pre>



<h3 id="RpcRequestMessageHandler"><a href="#RpcRequestMessageHandler" class="headerlink" title="RpcRequestMessageHandler"></a>RpcRequestMessageHandler</h3><pre><code class="hljs java"><span class="hljs-meta">@ChannelHandler</span>.Sharable
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RpcRequestMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler</span>&lt;<span class="hljs-title">RpcRequestMessage</span>&gt; </span>&#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext ctx, RpcRequestMessage rpcMessage)</span> </span>&#123;
        RpcResponseMessage rpcResponseMessage = <span class="hljs-keyword">new</span> RpcResponseMessage();
        <span class="hljs-keyword">try</span> &#123;
            <span class="hljs-comment">// 设置返回值的属性</span>
            rpcResponseMessage.setSequenceId(rpcMessage.getSequenceId());
            <span class="hljs-comment">// 返回一个实例</span>
            HelloService service = (HelloService) ServicesFactory.getInstance(Class.forName(rpcMessage.getInterfaceName()));
            
            <span class="hljs-comment">// 通过反射调用方法，并获取返回值</span>
            Method method = service.getClass().getMethod(rpcMessage.getMethodName(), rpcMessage.getParameterTypes());
            <span class="hljs-comment">// 获得返回值</span>
            Object invoke = method.invoke(service, rpcMessage.getParameterValue());
            <span class="hljs-comment">// 设置返回值</span>
            rpcResponseMessage.setReturnValue(invoke);
        &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;
            e.printStackTrace();
            <span class="hljs-comment">// 设置异常</span>
            rpcResponseMessage.setExceptionValue(e);
        &#125;
    &#125;
    <span class="hljs-comment">// 向channel中写入Message</span>
    ctx.writeAndFlush(rpcResponseMessage);
&#125;</code></pre>

<p>远程调用方法主要是通过反射实现的，大致步骤如下</p>
<ul>
<li>通过<strong>请求消息传入被调入方法的各个参数</strong></li>
<li>通过<strong>全限定接口名，在map中查询到对应的类并实例化对象</strong></li>
<li>通过反射获取Method，并调用其invoke方法的<strong>返回值，并放入响应消息中</strong></li>
<li>若有<strong>异常需要捕获，并放入响应消息中</strong></li>
</ul>
<h3 id="RpcResponseMessageHandler"><a href="#RpcResponseMessageHandler" class="headerlink" title="RpcResponseMessageHandler"></a>RpcResponseMessageHandler</h3><pre><code class="hljs java"><span class="hljs-meta">@ChannelHandler</span>.Sharable
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RpcResponseMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler</span>&lt;<span class="hljs-title">RpcResponseMessage</span>&gt; </span>&#123;
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Logger log = LoggerFactory.getLogger(ChatServer<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext ctx, RpcResponseMessage msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        log.debug(<span class="hljs-string">"&#123;&#125;"</span>, msg);
        System.out.println((String)msg.getReturnValue());
    &#125;
&#125;</code></pre>



<h3 id="客户端发送消息"><a href="#客户端发送消息" class="headerlink" title="客户端发送消息"></a>客户端发送消息</h3><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RPCClient</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
		...
           
        <span class="hljs-comment">// 创建请求并发送</span>
		RpcRequestMessage message = <span class="hljs-keyword">new</span> RpcRequestMessage(<span class="hljs-number">1</span>,
               <span class="hljs-string">"cn.nyimac.study.day8.server.service.HelloService"</span>,
               <span class="hljs-string">"sayHello"</span>,
               String<span class="hljs-class">.<span class="hljs-keyword">class</span>,</span>
               new Class[]&#123;String.class&#125;,
               <span class="hljs-keyword">new</span> Object[]&#123;<span class="hljs-string">"Nyima"</span>&#125;);
		
        channel.writeAndFlush(message);   
            
        ...    
    &#125;
&#125;</code></pre>



<p><strong>运行结果</strong></p>
<p>客户端</p>
<pre><code class="hljs java"><span class="hljs-number">1606</span> [nioEventLoopGroup-<span class="hljs-number">2</span>-<span class="hljs-number">1</span>] DEBUG cn.nyimac.study.day8.server.ChatServer  - RpcResponseMessage&#123;returnValue=你好，Nyima, exceptionValue=<span class="hljs-keyword">null</span>&#125;</code></pre>



<h3 id="改进客户端"><a href="#改进客户端" class="headerlink" title="改进客户端"></a>改进客户端</h3><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RPCClientManager</span> </span>&#123;
    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 产生SequenceId</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> AtomicInteger sequenceId = <span class="hljs-keyword">new</span> AtomicInteger(<span class="hljs-number">0</span>);
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">volatile</span> Channel channel = <span class="hljs-keyword">null</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Object lock = <span class="hljs-keyword">new</span> Object();
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        <span class="hljs-comment">// 创建代理对象</span>
        HelloService service = (HelloService) getProxy(HelloService<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
        <span class="hljs-comment">// 通过代理对象执行方法</span>
        System.out.println(service.sayHello(<span class="hljs-string">"Nyima"</span>));
        System.out.println(service.sayHello(<span class="hljs-string">"Hulu"</span>));
    &#125;

    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 单例模式创建Channel</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Channel <span class="hljs-title">getChannel</span><span class="hljs-params">()</span> </span>&#123;
        <span class="hljs-keyword">if</span> (channel == <span class="hljs-keyword">null</span>) &#123;
            <span class="hljs-keyword">synchronized</span> (lock) &#123;
                <span class="hljs-keyword">if</span> (channel == <span class="hljs-keyword">null</span>) &#123;
                    init();
                &#125;
            &#125;
        &#125;
        <span class="hljs-keyword">return</span> channel;
    &#125;

    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 使用代理模式，帮助我们创建请求消息并发送</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title">getProxy</span><span class="hljs-params">(Class&lt;?&gt; serviceClass)</span> </span>&#123;
        Class&lt;?&gt;[] classes = <span class="hljs-keyword">new</span> Class&lt;?&gt;[]&#123;serviceClass&#125;;
        <span class="hljs-comment">// 使用JDK代理，创建代理对象</span>
        Object o = Proxy.newProxyInstance(serviceClass.getClassLoader(), classes, <span class="hljs-keyword">new</span> InvocationHandler() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable </span>&#123;
                <span class="hljs-comment">// 创建请求消息</span>
                <span class="hljs-keyword">int</span> id = sequenceId.getAndIncrement();
                RpcRequestMessage message = <span class="hljs-keyword">new</span> RpcRequestMessage(id, serviceClass.getName(),
                        method.getName(), method.getReturnType(),
                        method.getParameterTypes(),
                        args);
                <span class="hljs-comment">// 发送消息</span>
                getChannel().writeAndFlush(message);

                <span class="hljs-comment">// 创建Promise，用于获取NIO线程中的返回结果，获取的过程是异步的</span>
                DefaultPromise&lt;Object&gt; promise = <span class="hljs-keyword">new</span> DefaultPromise&lt;&gt;(getChannel().eventLoop());
                <span class="hljs-comment">// 将Promise放入Map中</span>
                RpcResponseMessageHandler.promiseMap.put(id, promise);
                <span class="hljs-comment">// 等待被放入Promise中结果</span>
                promise.await();
                <span class="hljs-keyword">if</span> (promise.isSuccess()) &#123;
                    <span class="hljs-comment">// 调用方法成功，返回方法执行结果</span>
                    <span class="hljs-keyword">return</span> promise.getNow();
                &#125; <span class="hljs-keyword">else</span> &#123;
                    <span class="hljs-comment">// 调用方法失败，抛出异常</span>
                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(promise.cause());
                &#125;
            &#125;
        &#125;);
        <span class="hljs-keyword">return</span> o;
    &#125;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span> </span>&#123;
        NioEventLoopGroup group = <span class="hljs-keyword">new</span> NioEventLoopGroup();
        LoggingHandler loggingHandler = <span class="hljs-keyword">new</span> LoggingHandler(LogLevel.DEBUG);
        MessageSharableCodec messageSharableCodec = <span class="hljs-keyword">new</span> MessageSharableCodec();

        <span class="hljs-comment">// PRC 请求消息处理器</span>
        RpcResponseMessageHandler rpcResponseMessageHandler = <span class="hljs-keyword">new</span> RpcResponseMessageHandler();

        Bootstrap bootstrap = <span class="hljs-keyword">new</span> Bootstrap();
        bootstrap.channel(NioSocketChannel<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
        bootstrap.group(group);
        bootstrap.handler(<span class="hljs-keyword">new</span> ChannelInitializer&lt;SocketChannel&gt;() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                ch.pipeline().addLast(<span class="hljs-keyword">new</span> ProtocolFrameDecoder());
                ch.pipeline().addLast(loggingHandler);
                ch.pipeline().addLast(messageSharableCodec);
                ch.pipeline().addLast(rpcResponseMessageHandler);
            &#125;
        &#125;);
        <span class="hljs-keyword">try</span> &#123;
            channel = bootstrap.connect(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-string">"localhost"</span>, <span class="hljs-number">8080</span>)).sync().channel();
            <span class="hljs-comment">// 异步关闭 group，避免Channel被阻塞</span>
            channel.closeFuture().addListener(future -&gt; &#123;
                group.shutdownGracefully();
            &#125;);
        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;
            e.printStackTrace();
        &#125;
    &#125;
&#125;</code></pre>

<p><strong>获得Channel</strong></p>
<ul>
<li><p>建立连接，获取Channel的操作被封装到了<code>init</code>方法中，当连接断开时，通过<code>addListener</code>方<strong>法异步关闭group</strong></p>
</li>
<li><p>通过<strong>单例模式</strong>创建与获取Channel</p>
</li>
</ul>
<p><strong>远程调用方法</strong></p>
<ul>
<li>为了让方法的调用变得简洁明了，将<code>RpcRequestMessage</code>的<strong>创建与发送过程通过JDK的动态代理来完成</strong></li>
<li>通过返回的代理对象调用方法即可，<strong>方法参数为被调用方法接口的Class类</strong></li>
</ul>
<p><strong>远程调用方法返回值获取</strong></p>
<ul>
<li><p>调用方法的是主线程，处理返回结果的是NIO线程（RpcResponseMessageHandler）。<strong>要在不同线程中进行返回值的传递，需要用到Promise</strong></p>
</li>
<li><p>在<code>RpcResponseMessageHandler</code>中创建一个Map</p>
<ul>
<li>Key为<strong>SequenceId</strong></li>
<li>Value为对应的<strong>Promise</strong></li>
</ul>
</li>
<li><p><strong>主线程</strong>的代理类将RpcResponseMessage发送给服务器后，需要创建Promise对象，并将其放入到RpcResponseMessageHandler的Map中。<strong>需要使用await等待结果被放入Promise中</strong>。获取结果后，根据结果类型（判断是否成功）来返回结果或抛出异常</p>
<pre><code class="hljs java"><span class="hljs-comment">// 创建Promise，用于获取NIO线程中的返回结果，获取的过程是异步的</span>
DefaultPromise&lt;Object&gt; promise = <span class="hljs-keyword">new</span> DefaultPromise&lt;&gt;(getChannel().eventLoop());
<span class="hljs-comment">// 将Promise放入Map中</span>
RpcResponseMessageHandler.promiseMap.put(id, promise);
<span class="hljs-comment">// 等待被放入Promise中结果</span>
promise.await();
<span class="hljs-keyword">if</span> (promise.isSuccess()) &#123;
    <span class="hljs-comment">// 调用方法成功，返回方法执行结果</span>
    <span class="hljs-keyword">return</span> promise.getNow();
&#125; <span class="hljs-keyword">else</span> &#123;
    <span class="hljs-comment">// 调用方法失败，抛出异常</span>
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(promise.cause());
&#125;</code></pre>
</li>
<li><p><strong>NIO线程</strong>负责通过SequenceId<strong>获取并移除（remove）</strong>对应的Promise，然后根据RpcResponseMessage中的结果，向Promise中放入不同的值</p>
<ul>
<li>如果<strong>没有异常信息</strong>（ExceptionValue），就调用<code>promise.setSuccess(returnValue)</code>放入方法返回值</li>
<li>如果<strong>有异常信息</strong>，就调用<code>promise.setFailure(exception)</code>放入异常信息</li>
</ul>
<pre><code class="hljs java"><span class="hljs-comment">// 将返回结果放入对应的Promise中，并移除Map中的Promise</span>
Promise&lt;Object&gt; promise = promiseMap.remove(msg.getSequenceId());
Object returnValue = msg.getReturnValue();
Exception exception = msg.getExceptionValue();
<span class="hljs-keyword">if</span> (promise != <span class="hljs-keyword">null</span>) &#123;
    <span class="hljs-keyword">if</span> (exception != <span class="hljs-keyword">null</span>) &#123;
        <span class="hljs-comment">// 返回结果中有异常信息</span>
        promise.setFailure(exception);
    &#125; <span class="hljs-keyword">else</span> &#123;
        <span class="hljs-comment">// 方法正常执行，没有异常</span>
        promise.setSuccess(returnValue);
    &#125;
&#125;</code></pre>



</li>
</ul>
<h3 id="改进RpcResponseMessageHandler"><a href="#改进RpcResponseMessageHandler" class="headerlink" title="改进RpcResponseMessageHandler"></a>改进RpcResponseMessageHandler</h3><pre><code class="hljs java"><span class="hljs-meta">@ChannelHandler</span>.Sharable
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RpcResponseMessageHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SimpleChannelInboundHandler</span>&lt;<span class="hljs-title">RpcResponseMessage</span>&gt; </span>&#123;
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Logger log = LoggerFactory.getLogger(ChatServer<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;

    <span class="hljs-comment">/**</span>
<span class="hljs-comment">     * 用于存放Promise的集合，Promise用于主线程与NIO线程之间传递返回值</span>
<span class="hljs-comment">     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Map&lt;Integer, Promise&lt;Object&gt;&gt; promiseMap = <span class="hljs-keyword">new</span> ConcurrentHashMap&lt;&gt;(<span class="hljs-number">16</span>);

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead0</span><span class="hljs-params">(ChannelHandlerContext ctx, RpcResponseMessage msg)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        <span class="hljs-comment">// 将返回结果放入对应的Promise中，并移除Map中的Promise</span>
        Promise&lt;Object&gt; promise = promiseMap.remove(msg.getSequenceId());
        Object returnValue = msg.getReturnValue();
        Exception exception = msg.getExceptionValue();
        <span class="hljs-keyword">if</span> (promise != <span class="hljs-keyword">null</span>) &#123;
            <span class="hljs-keyword">if</span> (exception != <span class="hljs-keyword">null</span>) &#123;
                <span class="hljs-comment">// 返回结果中有异常信息</span>
                promise.setFailure(exception);
            &#125; <span class="hljs-keyword">else</span> &#123;
                <span class="hljs-comment">// 方法正常执行，没有异常</span>
                promise.setSuccess(returnValue);
            &#125;
        &#125;
        <span class="hljs-comment">// 拿到返回结果并打印</span>
        log.debug(<span class="hljs-string">"&#123;&#125;"</span>, msg);
    &#125;
&#125;</code></pre>



<h1 id="六、源码"><a href="#六、源码" class="headerlink" title="六、源码"></a>六、源码</h1><h2 id="1、启动流程"><a href="#1、启动流程" class="headerlink" title="1、启动流程"></a>1、启动流程</h2><p>Netty启动流程可以简化成如下代码</p>
<pre><code class="hljs java"><span class="hljs-comment">// netty 中使用 NioEventLoopGroup （简称 nio boss 线程）来封装线程和 selector</span>
Selector selector = Selector.open(); 

<span class="hljs-comment">// 创建 NioServerSocketChannel，同时会初始化它关联的 handler，以及为原生 ssc 存储 config</span>
NioServerSocketChannel attachment = <span class="hljs-keyword">new</span> NioServerSocketChannel();

<span class="hljs-comment">// 创建 NioServerSocketChannel 时，创建了 java 原生的 ServerSocketChannel</span>
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 
serverSocketChannel.configureBlocking(<span class="hljs-keyword">false</span>);

<span class="hljs-comment">// 启动 nio boss 线程执行接下来的操作</span>

<span class="hljs-comment">//注册（仅关联 selector 和 NioServerSocketChannel），未关注事件</span>
SelectionKey selectionKey = serverSocketChannel.register(selector, <span class="hljs-number">0</span>, attachment);

<span class="hljs-comment">// head -&gt; 初始化器 -&gt; ServerBootstrapAcceptor -&gt; tail，初始化器是一次性的，只为添加 acceptor</span>

<span class="hljs-comment">// 绑定端口</span>
serverSocketChannel.bind(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-number">8080</span>));

<span class="hljs-comment">// 触发 channel active 事件，在 head 中关注 op_accept 事件</span>
selectionKey.interestOps(SelectionKey.OP_ACCEPT);</code></pre>

<ul>
<li>获得选择器Selector，Netty中使用NioEventloopGroup中的NioEventloop封装了线程和选择器</li>
<li>创建<code>NioServerSocketChannel</code>，该Channel<strong>作为附件</strong>添加到<code>ServerSocketChannel</code>中</li>
<li>创建<code>ServerSocketChannel</code>，将其设置为非阻塞模式，并注册到Selector中，<strong>此时未关注事件，但是添加了附件</strong><code>NioServerSocketChannel</code></li>
<li>绑定端口</li>
<li>通过<code>interestOps</code>设置感兴趣的事件</li>
</ul>
<h3 id="bind"><a href="#bind" class="headerlink" title="bind"></a>bind</h3><p>选择器Selector的创建是在NioEventloopGroup中完成的。<strong>NioServerSocketChannel与ServerSocketChannel的创建，ServerSocketChannel注册到Selector中以及绑定操作都是由<code>bind</code>方法完成的</strong></p>
<p>所以服务器启动的<strong>入口</strong>便是<code>io.netty.bootstrap.ServerBootstrap.bind</code></p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> ChannelFuture <span class="hljs-title">bind</span><span class="hljs-params">(SocketAddress localAddress)</span> </span>&#123;
	validate();
	<span class="hljs-keyword">return</span> doBind(ObjectUtil.checkNotNull(localAddress, <span class="hljs-string">"localAddress"</span>));
&#125;</code></pre>



<h3 id="doBind"><a href="#doBind" class="headerlink" title="doBind"></a>doBind</h3><p>真正完成初始化、注册以及绑定的<strong>方法是<code>io.netty.bootstrap.AbstractBootstrap.doBind</code></strong></p>
<p><strong>dobind方法在主线程中执行</strong></p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> ChannelFuture <span class="hljs-title">doBind</span><span class="hljs-params">(<span class="hljs-keyword">final</span> SocketAddress localAddress)</span> </span>&#123;
    <span class="hljs-comment">// 负责NioServerSocketChannel和ServerSocketChannel的创建</span>
    <span class="hljs-comment">// ServerSocketChannel的注册工作</span>
    <span class="hljs-comment">// init由main线程完成，regisetr由NIO线程完成</span>
    <span class="hljs-keyword">final</span> ChannelFuture regFuture = initAndRegister();
    <span class="hljs-keyword">final</span> Channel channel = regFuture.channel();
    <span class="hljs-keyword">if</span> (regFuture.cause() != <span class="hljs-keyword">null</span>) &#123;
        <span class="hljs-keyword">return</span> regFuture;
    &#125;

    <span class="hljs-comment">// 因为register操作是异步的</span>
    <span class="hljs-comment">// 所以要判断主线程执行到这里时，register操作是否已经执行完毕</span>
    <span class="hljs-keyword">if</span> (regFuture.isDone()) &#123;
        <span class="hljs-comment">// At this point we know that the registration was complete and successful.</span>
        ChannelPromise promise = channel.newPromise();
        
        <span class="hljs-comment">// 执行doBind0绑定操作</span>
        doBind0(regFuture, channel, localAddress, promise);
        <span class="hljs-keyword">return</span> promise;
    &#125; <span class="hljs-keyword">else</span> &#123;
        <span class="hljs-comment">// Registration future is almost always fulfilled already, but just in case it's not.</span>
        <span class="hljs-comment">// 如果register操作还没执行完，就会到这个分支中来</span>
        <span class="hljs-keyword">final</span> PendingRegistrationPromise promise = <span class="hljs-keyword">new</span> PendingRegistrationPromise(channel);
        
        <span class="hljs-comment">// 添加监听器，NIO线程异步进行doBind0操作</span>
        regFuture.addListener(<span class="hljs-keyword">new</span> ChannelFutureListener() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">operationComplete</span><span class="hljs-params">(ChannelFuture future)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                Throwable cause = future.cause();
                <span class="hljs-keyword">if</span> (cause != <span class="hljs-keyword">null</span>) &#123;
                    <span class="hljs-comment">// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an</span>
                    <span class="hljs-comment">// IllegalStateException once we try to access the EventLoop of the Channel.</span>
                    promise.setFailure(cause);
                &#125; <span class="hljs-keyword">else</span> &#123;
                    <span class="hljs-comment">// Registration was successful, so set the correct executor to use.</span>
                    <span class="hljs-comment">// See https://github.com/netty/netty/issues/2586</span>
                    promise.registered();

                    doBind0(regFuture, channel, localAddress, promise);
                &#125;
            &#125;
        &#125;);
        <span class="hljs-keyword">return</span> promise;
    &#125;
&#125;</code></pre>

<ul>
<li><p>doBind()中有两个重要方法<code>initAndRegister()</code>和<code>doBind0(regFuture, channel, localAddress, promise)</code></p>
</li>
<li><p><strong>initAndRegister</strong>主要负责NioServerSocketChannel和ServerSocketChannel的创建（主线程中完成）与ServerSocketChannel注册（NIO线程中完成）工作</p>
</li>
<li><p><strong>doBind0</strong>则负责连接的创建工作</p>
</li>
</ul>
<h3 id="initAndRegisterd"><a href="#initAndRegisterd" class="headerlink" title="initAndRegisterd"></a>initAndRegisterd</h3><p>代码</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">final</span> ChannelFuture <span class="hljs-title">initAndRegister</span><span class="hljs-params">()</span> </span>&#123;
    Channel channel = <span class="hljs-keyword">null</span>;
    <span class="hljs-keyword">try</span> &#123;
        channel = channelFactory.newChannel();
        init(channel);
    &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;
        <span class="hljs-keyword">if</span> (channel != <span class="hljs-keyword">null</span>) &#123;
            <span class="hljs-comment">// channel can be null if newChannel crashed (eg SocketException("too many open files"))</span>
            channel.unsafe().closeForcibly();
            <span class="hljs-comment">// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor</span>
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
        &#125;
        <span class="hljs-comment">// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DefaultChannelPromise(<span class="hljs-keyword">new</span> FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
    &#125;

    ChannelFuture regFuture = config().group().register(channel);
    <span class="hljs-keyword">if</span> (regFuture.cause() != <span class="hljs-keyword">null</span>) &#123;
        <span class="hljs-keyword">if</span> (channel.isRegistered()) &#123;
            channel.close();
        &#125; <span class="hljs-keyword">else</span> &#123;
            channel.unsafe().closeForcibly();
        &#125;
    &#125;

    <span class="hljs-comment">// If we are here and the promise is not failed, it's one of the following cases:</span>
    <span class="hljs-comment">// 1) If we attempted registration from the event loop, the registration has been completed at this point.</span>
    <span class="hljs-comment">//    i.e. It's safe to attempt bind() or connect() now because the channel has been registered.</span>
    <span class="hljs-comment">// 2) If we attempted registration from the other thread, the registration request has been successfully</span>
    <span class="hljs-comment">//    added to the event loop's task queue for later execution.</span>
    <span class="hljs-comment">//    i.e. It's safe to attempt bind() or connect() now:</span>
    <span class="hljs-comment">//         because bind() or connect() will be executed *after* the scheduled registration task is executed</span>
    <span class="hljs-comment">//         because register(), bind(), and connect() are all bound to the same thread.</span>

    <span class="hljs-keyword">return</span> regFuture;
&#125;</code></pre>



<h4 id="init"><a href="#init" class="headerlink" title="init"></a>init</h4><pre><code class="hljs java">Channel channel = <span class="hljs-keyword">null</span>;
<span class="hljs-keyword">try</span> &#123;
    <span class="hljs-comment">// 通过反射初始化NioServerSocketChannel</span>
    channel = channelFactory.newChannel();
    init(channel);
&#125;</code></pre>

<p>newChannel方法</p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> T <span class="hljs-title">newChannel</span><span class="hljs-params">()</span> </span>&#123;
    <span class="hljs-keyword">try</span> &#123;
        <span class="hljs-comment">// 通过反射调用NioServerSocketChannel的构造方法</span>
        <span class="hljs-comment">// 创建NioServerSocketChannel对象</span>
        <span class="hljs-keyword">return</span> constructor.newInstance();
    &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ChannelException(<span class="hljs-string">"Unable to create Channel from class "</span> + constructor.getDeclaringClass(), t);
    &#125;
&#125;</code></pre>

<p>NioServerSocketChannel构造方法</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">NioServerSocketChannel</span><span class="hljs-params">()</span> </span>&#123;
    <span class="hljs-comment">// 创建了ServerSocketChannel实例</span>
    <span class="hljs-keyword">this</span>(newSocket(DEFAULT_SELECTOR_PROVIDER));
&#125;</code></pre>

<p>newSocket方法</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> ServerSocketChannel <span class="hljs-title">newSocket</span><span class="hljs-params">(SelectorProvider provider)</span> </span>&#123;
    <span class="hljs-keyword">try</span> &#123;
        <span class="hljs-comment">// ServerSocketChannel.open方法：</span>
        <span class="hljs-comment">// SelectorProvider.provider().openServerSocketChannel()</span>
	    <span class="hljs-comment">// 所以此处相当于ServerSocketChannel.open()</span>
        <span class="hljs-comment">// 创建了ServerSocketChannel实例</span>
    	<span class="hljs-keyword">return</span> provider.openServerSocketChannel();
	&#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;
  	  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ChannelException(<span class="hljs-string">"Failed to open a server socket."</span>, e);
	&#125;
&#125;</code></pre>

<p><strong>init方法</strong></p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">(Channel channel)</span> </span>&#123;
   	...
		
    <span class="hljs-comment">// NioSocketChannl的Pipeline    </span>
    ChannelPipeline p = channel.pipeline();
		
    ...

    <span class="hljs-comment">// 向Pipeline中添加了一个handler，该handler等待被调用</span>
    p.addLast(<span class="hljs-keyword">new</span> ChannelInitializer&lt;Channel&gt;() &#123;
        <span class="hljs-meta">@Override</span>
        <span class="hljs-comment">// register之后才调用该方法</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(<span class="hljs-keyword">final</span> Channel ch)</span> </span>&#123;
            <span class="hljs-keyword">final</span> ChannelPipeline pipeline = ch.pipeline();
            
            <span class="hljs-comment">// 创建handler并加入到pipeline中</span>
            ChannelHandler handler = config.handler();
            <span class="hljs-keyword">if</span> (handler != <span class="hljs-keyword">null</span>) &#123;
                pipeline.addLast(handler);
            &#125;

            ch.eventLoop().execute(<span class="hljs-keyword">new</span> Runnable() &#123;
                <span class="hljs-meta">@Override</span>
                <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
                    <span class="hljs-comment">// 添加新的handler，在发生Accept事件后建立连接</span>
                    pipeline.addLast(<span class="hljs-keyword">new</span> ServerBootstrapAcceptor(
                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                &#125;
            &#125;);
        &#125;
    &#125;);
&#125;</code></pre>

<p><strong>init主要完成了以下三个操作</strong></p>
<ul>
<li>创建NioServerSocketChannel</li>
<li>通过NioServerSocketChannel的构造器，创建了ServerSocketChannel</li>
<li>由<code>initChannel</code>方法向NioServerSocketChannel中添加了两个handler，<strong>添加操作在register之后被执行</strong><ul>
<li>一个handler负责设置配置</li>
<li>一个handler负责发生Accepet事件后建立连接</li>
</ul>
</li>
</ul>
<h4 id="Register"><a href="#Register" class="headerlink" title="Register"></a>Register</h4><p>init执行完毕后，便执行<code>ChannelFuture regFuture = config().group().register(channel)</code>操作</p>
<p>该方法最终调用的是<code>promise.channel().unsafe().register(this, promise)</code>方法</p>
<p><strong>promise.channel().unsafe().register(this, promise)</strong></p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title">register</span><span class="hljs-params">(EventLoop eventLoop, <span class="hljs-keyword">final</span> ChannelPromise promise)</span> </span>&#123;
    ...

    <span class="hljs-comment">// 获取EventLoop</span>
    AbstractChannel.<span class="hljs-keyword">this</span>.eventLoop = eventLoop;

   	<span class="hljs-comment">// 此处完成了由 主线程 到 NIO线程 的切换</span>
    <span class="hljs-comment">// eventLoop.inEventLoop()用于判断当前线程是否为NIO线程</span>
    <span class="hljs-keyword">if</span> (eventLoop.inEventLoop()) &#123;
        register0(promise);
    &#125; <span class="hljs-keyword">else</span> &#123;
        <span class="hljs-keyword">try</span> &#123;
            <span class="hljs-comment">// 向NIO线程中添加任务</span>
            eventLoop.execute(<span class="hljs-keyword">new</span> Runnable() &#123;
                <span class="hljs-meta">@Override</span>
                <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
                    <span class="hljs-comment">// 该方法中会执行doRegister</span>
                    <span class="hljs-comment">// 执行真正的注册操作</span>
                    register0(promise);
                &#125;
            &#125;);
        &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;
           ...
        &#125;
    &#125;
&#125;</code></pre>

<p>register0方法</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">register0</span><span class="hljs-params">(ChannelPromise promise)</span> </span>&#123;
    <span class="hljs-keyword">try</span> &#123;
       	...
            
        <span class="hljs-comment">// 执行真正的注册操作</span>
        doRegister();
        neverRegistered = <span class="hljs-keyword">false</span>;
        registered = <span class="hljs-keyword">true</span>;

        <span class="hljs-comment">// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the</span>
        <span class="hljs-comment">// user may already fire events through the pipeline in the ChannelFutureListener.</span>
        
        <span class="hljs-comment">// 调用init中的initChannel方法</span>
        pipeline.invokeHandlerAddedIfNeeded();

        ...
    &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;
        ...
    &#125;
&#125;</code></pre>

<p><strong>doRegister方法</strong></p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doRegister</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
    <span class="hljs-keyword">boolean</span> selected = <span class="hljs-keyword">false</span>;
    <span class="hljs-keyword">for</span> (;;) &#123;
        <span class="hljs-keyword">try</span> &#123;
            <span class="hljs-comment">// javaChannel()即为ServerSocketChannel</span>
            <span class="hljs-comment">// eventLoop().unwrappedSelector()获取eventLoop中的Selector</span>
            <span class="hljs-comment">// this为NIOServerSocketChannel，作为附件</span>
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), <span class="hljs-number">0</span>, <span class="hljs-keyword">this</span>);
            <span class="hljs-keyword">return</span>;
        &#125; <span class="hljs-keyword">catch</span> (CancelledKeyException e) &#123;
            ...
           
        &#125;
    &#125;
&#125;</code></pre>

<p>回调initChannel</p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initChannel</span><span class="hljs-params">(<span class="hljs-keyword">final</span> Channel ch)</span> </span>&#123;
    <span class="hljs-keyword">final</span> ChannelPipeline pipeline = ch.pipeline();
    ChannelHandler handler = config.handler();
    <span class="hljs-keyword">if</span> (handler != <span class="hljs-keyword">null</span>) &#123;
        pipeline.addLast(handler);
    &#125;

    <span class="hljs-comment">// 添加新任务，任务负责添加handler</span>
    <span class="hljs-comment">// 该handler负责发生Accepet事件后建立连接</span>
    ch.eventLoop().execute(<span class="hljs-keyword">new</span> Runnable() &#123;
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
            pipeline.addLast(<span class="hljs-keyword">new</span> ServerBootstrapAcceptor(
                    ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
        &#125;
    &#125;);
&#125;</code></pre>



<p>Register主要完成了以下三个操作</p>
<ul>
<li><p>完成了主线程到NIO的<strong>线程切换</strong></p>
<ul>
<li>通过<code>eventLoop.inEventLoop()</code>进行线程判断，判断当前线程是否为NIO线程</li>
<li>切换的方式为让eventLoop执行register的操作</li>
<li><strong>register的操作在NIO线程中完成</strong></li>
</ul>
</li>
<li><p><strong>调用doRegister方法</strong></p>
<pre><code class="hljs java"><span class="hljs-comment">// javaChannel()即为ServerSocketChannel</span>
<span class="hljs-comment">// eventLoop().unwrappedSelector()获取eventLoop中的Selector</span>
<span class="hljs-comment">// this为NIOServerSocketChannel，作为附件</span>
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), <span class="hljs-number">0</span>, <span class="hljs-keyword">this</span>);</code></pre>

<ul>
<li><strong>将ServerSocketChannel注册到EventLoop的Selector中</strong></li>
<li>此时还未关注事件</li>
<li>添加NioServerSocketChannel附件</li>
</ul>
</li>
<li><p>通过<code>invokeHandlerAddedIfNeeded</code>调用init中的<code>initChannel</code>方法</p>
<ul>
<li>initChannel方法主要创建了<strong>两个handler</strong><ul>
<li>一个handler负责设置配置</li>
<li>一个handler负责发生Accept事件后建立连接</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="doBind0"><a href="#doBind0" class="headerlink" title="doBind0"></a>doBind0</h3><h4 id="绑定端口"><a href="#绑定端口" class="headerlink" title="绑定端口"></a>绑定端口</h4><p>在<code>doRegister</code>和<code>invokeHandlerAddedIfNeeded</code>操作中的完成后，会调用<code>safeSetSuccess(promise)</code>方法，向Promise中设置执行成功的结果。此时<code>doBind</code>方法中由<code>initAndRegister</code>返回的ChannelFuture对象regFuture便会由NIO线程异步执行doBind0绑定操作</p>
<pre><code class="hljs java"><span class="hljs-comment">// initAndRegister为异步方法，会返回ChannelFuture对象</span>
<span class="hljs-keyword">final</span> ChannelFuture regFuture = initAndRegister();
regFuture.addListener(<span class="hljs-keyword">new</span> ChannelFutureListener() &#123;
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">operationComplete</span><span class="hljs-params">(ChannelFuture future)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        Throwable cause = future.cause();
        <span class="hljs-keyword">if</span> (cause != <span class="hljs-keyword">null</span>) &#123;
            <span class="hljs-comment">// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an</span>
            <span class="hljs-comment">// IllegalStateException once we try to access the EventLoop of the Channel.</span>
            promise.setFailure(cause);
        &#125; <span class="hljs-keyword">else</span> &#123;
            <span class="hljs-comment">// Registration was successful, so set the correct executor to use.</span>
            <span class="hljs-comment">// See https://github.com/netty/netty/issues/2586</span>
            promise.registered();
            <span class="hljs-comment">// 如果没有异常，则执行绑定操作</span>
            doBind0(regFuture, channel, localAddress, promise);
        &#125;
    &#125;
&#125;);</code></pre>

<p><strong>doBind0最底层调用的是ServerSocketChannel的bind方法</strong></p>
<p>NioServerSocketChannel.doBind方法</p>
<p>通过该方法，绑定了对应的端口</p>
<pre><code class="hljs java"><span class="hljs-meta">@SuppressJava</span>6Requirement(reason = <span class="hljs-string">"Usage guarded by java version check"</span>)
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doBind</span><span class="hljs-params">(SocketAddress localAddress)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
    <span class="hljs-keyword">if</span> (PlatformDependent.javaVersion() &gt;= <span class="hljs-number">7</span>) &#123;
        <span class="hljs-comment">// 调用ServerSocketChannel的bind方法，绑定端口</span>
        javaChannel().bind(localAddress, config.getBacklog());
    &#125; <span class="hljs-keyword">else</span> &#123;
        javaChannel().socket().bind(localAddress, config.getBacklog());
    &#125;
&#125;</code></pre>



<h4 id="关注事件"><a href="#关注事件" class="headerlink" title="关注事件"></a>关注事件</h4><p>在绑定端口操作完成后，会判断各种所有初始化操作是否已经完成，若完成，则会添加ServerSocketChannel感兴趣的事件</p>
<pre><code class="hljs java"><span class="hljs-keyword">if</span> (!wasActive &amp;&amp; isActive()) &#123;
    invokeLater(<span class="hljs-keyword">new</span> Runnable() &#123;
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
            pipeline.fireChannelActive();
        &#125;
    &#125;);
&#125;</code></pre>

<p>最终在<code>AbstractNioChannel.doBeginRead</code>方法中，会添加ServerSocketChannel添加Accept事件</p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doBeginRead</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
    <span class="hljs-comment">// Channel.read() or ChannelHandlerContext.read() was called</span>
    <span class="hljs-keyword">final</span> SelectionKey selectionKey = <span class="hljs-keyword">this</span>.selectionKey;
    <span class="hljs-keyword">if</span> (!selectionKey.isValid()) &#123;
        <span class="hljs-keyword">return</span>;
    &#125;
    readPending = <span class="hljs-keyword">true</span>;
    <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> interestOps = selectionKey.interestOps();
    <span class="hljs-comment">// 如果ServerSocketChannel没有关注Accept事件</span>
    <span class="hljs-keyword">if</span> ((interestOps &amp; readInterestOp) == <span class="hljs-number">0</span>) &#123;
        <span class="hljs-comment">// 则让其关注Accepet事件</span>
        <span class="hljs-comment">// readInterestOp 取值是 16</span>
        <span class="hljs-comment">// 在 NioServerSocketChannel 创建时初始化</span>
        selectionKey.interestOps(interestOps | readInterestOp);
    &#125;
&#125;</code></pre>

<p><strong>注意</strong>：此处设置interestOps时使用的方法，<strong>避免覆盖关注的其他事件</strong></p>
<ul>
<li><p>首先获取Channel所有感兴趣的事件</p>
<pre><code class="hljs java"><span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> interestOps = selectionKey.interestOps();</code></pre>
</li>
<li><p>然后再设置其感兴趣的事件</p>
<pre><code class="hljs java">selectionKey.interestOps(interestOps | readInterestOp);</code></pre>



</li>
</ul>
<p><strong>各个事件对应的值</strong></p>
<p><img src="https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20210506090047.png" srcset="/img/loading.gif" alt=""></p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过上述步骤，完成了</p>
<ul>
<li>NioServerSocketChannel与ServerSocketChannel的创建</li>
<li>ServerSocketChannel绑定到EventLoop的Selecot中，并添加NioServerSocketChannel附件</li>
<li>绑定了对应的端口</li>
<li>关注了Accept事件</li>
</ul>
<h2 id="2、NioEventLoop剖析"><a href="#2、NioEventLoop剖析" class="headerlink" title="2、NioEventLoop剖析"></a>2、NioEventLoop剖析</h2><h3 id="组成-1"><a href="#组成-1" class="headerlink" title="组成"></a>组成</h3><p>NioEventLoop的重要组成部分有三个</p>
<ul>
<li><p><strong>Selector</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NioEventLoop</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SingleThreadEventLoop</span> </span>&#123;
    
    ...
        
    <span class="hljs-comment">// selector中的selectedKeys是基于数组的</span>
    <span class="hljs-comment">// unwrappedSelector中的selectedKeys是基于HashSet的    </span>
    <span class="hljs-keyword">private</span> Selector selector;
    <span class="hljs-keyword">private</span> Selector unwrappedSelector;
    <span class="hljs-keyword">private</span> SelectedSelectionKeySet selectedKeys;
    
    ...
&#125;</code></pre>
</li>
<li><p><strong>Thread与TaskQueue</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SingleThreadEventExecutor</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractScheduledEventExecutor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">OrderedEventExecutor</span> </span>&#123;
    <span class="hljs-comment">// 任务队列</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Queue&lt;Runnable&gt; taskQueue;

    <span class="hljs-comment">// 线程</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">volatile</span> Thread thread;
&#125;</code></pre>



</li>
</ul>
<h4 id="Selector的创建"><a href="#Selector的创建" class="headerlink" title="Selector的创建"></a>Selector的创建</h4><p>Selector是在NioEventLoop的构造方法中被创建的</p>
<pre><code class="hljs java">NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider, SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler, EventLoopTaskQueueFactory queueFactory) &#123;
    
        ...
           
        <span class="hljs-comment">// 初始化selector，初始化过程在openSelector中</span>
        <span class="hljs-keyword">final</span> SelectorTuple selectorTuple = openSelector();
        <span class="hljs-keyword">this</span>.selector = selectorTuple.selector;
        <span class="hljs-keyword">this</span>.unwrappedSelector = selectorTuple.unwrappedSelector;
&#125;


<span class="hljs-function"><span class="hljs-keyword">private</span> SelectorTuple <span class="hljs-title">openSelector</span><span class="hljs-params">()</span> </span>&#123;
    <span class="hljs-keyword">final</span> Selector unwrappedSelector;
    <span class="hljs-keyword">try</span> &#123;
        <span class="hljs-comment">// 此处等同于 Selector.open()方法</span>
        <span class="hljs-comment">// 创建了unwrappedSelector对象</span>
        unwrappedSelector = provider.openSelector();
    &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ChannelException(<span class="hljs-string">"failed to open a new selector"</span>, e);
    &#125;
&#125;</code></pre>

<p>NioEventLoop的构造方法中，调用了<code>openSelector()</code>方法， 该方法会返回一个</p>
<p><strong>SelectorTuple</strong>对象，该方法<strong>是创建Selector的核心方法</strong>。<code>openSelector()</code>方法内部调用了</p>
<pre><code class="hljs java">unwrappedSelector = provider.openSelector();</code></pre>

<p>获得了Selector对象<code>unwrappedSelector</code></p>
<p>后面会通过反射，修改<code>unwrappedSelector</code>中SelectedKeys的实现，然后通过<code>SelectedSelectionKeySetSelector</code>方法获得selector。<strong>最后通过SelectorTuple的构造方法，将该Selector的值赋给SelectorTuple类中的selector与unwrappedSelector</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SelectorTuple</span> </span>&#123;
    <span class="hljs-keyword">final</span> Selector unwrappedSelector;
    <span class="hljs-keyword">final</span> Selector selector;

    SelectorTuple(Selector unwrappedSelector) &#123;
        <span class="hljs-keyword">this</span>.unwrappedSelector = unwrappedSelector;
        <span class="hljs-keyword">this</span>.selector = unwrappedSelector;
    &#125;

    <span class="hljs-comment">/**</span>
<span class="hljs-comment">    * 一般调用的是这个构造方法</span>
<span class="hljs-comment">    */</span>
    SelectorTuple(Selector unwrappedSelector, Selector selector) &#123;
        <span class="hljs-keyword">this</span>.unwrappedSelector = unwrappedSelector;
        <span class="hljs-keyword">this</span>.selector = selector;
    &#125;
&#125;</code></pre>

<p><strong>再通过NioEventLoop的构造方法，将SelectorTuple中的Selector赋值给NioEventLoop中的Selector</strong></p>
<h4 id="两个Selector"><a href="#两个Selector" class="headerlink" title="两个Selector"></a>两个Selector</h4><p>NioEventLoop中有selector和unwrappedSelector两个Selector，<strong>它们的区别主要在于SelectedKeys的数据结构</strong></p>
<ul>
<li>selector中的SelectedKeys是<strong>基于数组</strong>的</li>
<li>unwrappedSelector中的是<strong>基于HashSet</strong>的</li>
</ul>
<p>这样做的主要目的是，<strong>数组的遍历效率要高于HashSet</strong></p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> SelectorTuple <span class="hljs-title">openSelector</span><span class="hljs-params">()</span> </span>&#123;
    <span class="hljs-keyword">final</span> Selector unwrappedSelector;
    <span class="hljs-keyword">try</span> &#123;
        unwrappedSelector = provider.openSelector();
    &#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ChannelException(<span class="hljs-string">"failed to open a new selector"</span>, e);
    &#125;

    ...
    
    <span class="hljs-comment">// 获得基于数组的selectedKeySet实现</span>
    <span class="hljs-keyword">final</span> SelectedSelectionKeySet selectedKeySet = <span class="hljs-keyword">new</span> SelectedSelectionKeySet();


    Object maybeException = AccessController.doPrivileged(<span class="hljs-keyword">new</span> PrivilegedAction&lt;Object&gt;() &#123;
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
            <span class="hljs-keyword">try</span> &#123;
                <span class="hljs-comment">// 通过反射拿到unwrappedSelector中的selectedKeys属性</span>
                Field selectedKeysField = selectorImplClass.getDeclaredField(<span class="hljs-string">"selectedKeys"</span>);
                Field publicSelectedKeysField = selectorImplClass.getDeclaredField(<span class="hljs-string">"publicSelectedKeys"</span>);

                ...
	
                <span class="hljs-comment">// 暴力反射，修改私有属性</span>
                Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, <span class="hljs-keyword">true</span>);
                <span class="hljs-keyword">if</span> (cause != <span class="hljs-keyword">null</span>) &#123;
                    <span class="hljs-keyword">return</span> cause;
                &#125;
                cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, <span class="hljs-keyword">true</span>);
                <span class="hljs-keyword">if</span> (cause != <span class="hljs-keyword">null</span>) &#123;
                    <span class="hljs-keyword">return</span> cause;
                &#125;

                <span class="hljs-comment">// 替换为基于数组的selectedKeys实现</span>
                selectedKeysField.set(unwrappedSelector, selectedKeySet);
                publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
            &#125; <span class="hljs-keyword">catch</span> (NoSuchFieldException e) &#123;
                <span class="hljs-keyword">return</span> e;
            &#125; <span class="hljs-keyword">catch</span> (IllegalAccessException e) &#123;
                <span class="hljs-keyword">return</span> e;
            &#125;
        &#125;
    &#125;);

    selectedKeys = selectedKeySet;
    
    <span class="hljs-comment">// 调用构造函数，创建unwrappedSelector与selector</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> SelectorTuple(unwrappedSelector,
                             <span class="hljs-keyword">new</span> SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
&#125;</code></pre>

<p>获得数组实现SelectedKeys的Selector的原理是反射，主要步骤如下</p>
<ul>
<li><p>获得<strong>基于数组</strong>的selectedKeySet实现</p>
<pre><code class="hljs java"><span class="hljs-comment">// 获得基于数组的selectedKeySet实现</span>
<span class="hljs-keyword">final</span> SelectedSelectionKeySet selectedKeySet = <span class="hljs-keyword">new</span> SelectedSelectionKeySet();

SelectedSelectionKeySet() &#123;
	keys = <span class="hljs-keyword">new</span> SelectionKey[<span class="hljs-number">1024</span>];
&#125;</code></pre>
</li>
<li><p>通过<strong>反射</strong>拿到unwrappedSelector中的SelectedKeySet并将其<strong>替换为selectedKeySet</strong></p>
</li>
<li><p><strong>通过Selector的构造方法</strong>获得selector</p>
<pre><code class="hljs java"><span class="hljs-keyword">new</span> SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet)</code></pre>
</li>
<li><p><strong>通过SelectorTuple的构造方法</strong>获得拥有两种Selector的SelectorTuple对象，并返回给NioEventLoop</p>
<pre><code class="hljs java"><span class="hljs-comment">// 调用构造函数，创建unwrappedSelector与selector</span>
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> SelectorTuple(unwrappedSelector, <span class="hljs-keyword">new</span> SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));</code></pre>





</li>
</ul>
<h3 id="NIO线程启动时机"><a href="#NIO线程启动时机" class="headerlink" title="NIO线程启动时机"></a>NIO线程启动时机</h3><h4 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h4><p>NioEventLoop中的线程，<strong>在首次执行任务时，才会被创建，且只会被创建一次</strong></p>
<p><strong>测试代码</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestNioEventLoop</span> </span>&#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>&#123;
        EventLoop eventLoop = <span class="hljs-keyword">new</span> NioEventLoopGroup().next();
        <span class="hljs-comment">// 使用NioEventLoop执行任务</span>
        eventLoop.execute(()-&gt;&#123;
            System.out.println(<span class="hljs-string">"hello"</span>);
        &#125;);
    &#125;
&#125;</code></pre>

<p>进入<code>execute</code>执行任务</p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">execute</span><span class="hljs-params">(Runnable task)</span> </span>&#123;
    <span class="hljs-comment">// 检测传入的任务是否为空，为空会抛出NullPointerException</span>
    ObjectUtil.checkNotNull(task, <span class="hljs-string">"task"</span>);
    <span class="hljs-comment">// 执行任务</span>
    <span class="hljs-comment">// 此处判断了任务是否为懒加载任务，wakesUpForTask的返回值只会为true</span>
    execute(task, !(task <span class="hljs-keyword">instanceof</span> LazyRunnable) &amp;&amp; wakesUpForTask(task));
&#125;</code></pre>

<p>进入上述代码的<code>execute</code>方法</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">execute</span><span class="hljs-params">(Runnable task, <span class="hljs-keyword">boolean</span> immediate)</span> </span>&#123;
    <span class="hljs-comment">// 判断当前线程是否为NIO线程</span>
    <span class="hljs-comment">// 判断方法为 return thread == this.thread;</span>
    <span class="hljs-comment">// this.thread即为NIO线程，首次执行任务时，其为null</span>
    <span class="hljs-keyword">boolean</span> inEventLoop = inEventLoop();
    
    <span class="hljs-comment">// 向任务队列taskQueue中添加任务</span>
    addTask(task);
    
    <span class="hljs-comment">// 当前线程不是NIO线程，则进入if语句</span>
    <span class="hljs-keyword">if</span> (!inEventLoop) &#123;
        <span class="hljs-comment">// 启动NIO线程的核心方法</span>
        startThread();
        
        ...
        
    &#125;
	
    <span class="hljs-comment">// 有任务需要被执行时，唤醒阻塞的NIO线程</span>
    <span class="hljs-keyword">if</span> (!addTaskWakesUp &amp;&amp; immediate) &#123;
        wakeup(inEventLoop);
    &#125;
&#125;</code></pre>

<p>进入<code>startThread</code>方法</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">startThread</span><span class="hljs-params">()</span> </span>&#123;
    <span class="hljs-comment">// 查看NIO线程状态是否为未启动</span>
    <span class="hljs-comment">// 该if代码块只会执行一次</span>
    <span class="hljs-comment">// state一开始的值就是ST_NOT_STARTED</span>
    <span class="hljs-comment">// private volatile int state = ST_NOT_STARTED;</span>
    <span class="hljs-keyword">if</span> (state == ST_NOT_STARTED) &#123;
        <span class="hljs-comment">// 通过原子属性更新器将状态更新为启动（ST_STARTED）</span>
        <span class="hljs-keyword">if</span> (STATE_UPDATER.compareAndSet(<span class="hljs-keyword">this</span>, ST_NOT_STARTED, ST_STARTED)) &#123;
            <span class="hljs-keyword">boolean</span> success = <span class="hljs-keyword">false</span>;
            <span class="hljs-keyword">try</span> &#123;
                <span class="hljs-comment">// 执行启动线程</span>
                doStartThread();
                success = <span class="hljs-keyword">true</span>;
            &#125; <span class="hljs-keyword">finally</span> &#123;
                <span class="hljs-keyword">if</span> (!success) &#123;
                    STATE_UPDATER.compareAndSet(<span class="hljs-keyword">this</span>, ST_STARTED, ST_NOT_STARTED);
                &#125;
            &#125;
        &#125;
    &#125;
&#125;</code></pre>

<p><strong>进入<code>doStartThread</code>，真正创建NIO线程并执行任务</strong></p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doStartThread</span><span class="hljs-params">()</span> </span>&#123;
    <span class="hljs-keyword">assert</span> thread == <span class="hljs-keyword">null</span>;
    <span class="hljs-comment">// 创建NIO线程并执行任务</span>
    executor.execute(<span class="hljs-keyword">new</span> Runnable() &#123;
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
            <span class="hljs-comment">// thread即为NIO线程</span>
            thread = Thread.currentThread();
            <span class="hljs-keyword">if</span> (interrupted) &#123;
                thread.interrupt();
            &#125;

            <span class="hljs-keyword">boolean</span> success = <span class="hljs-keyword">false</span>;
            updateLastExecutionTime();
            <span class="hljs-keyword">try</span> &#123;
                <span class="hljs-comment">// 执行内部run方法</span>
                SingleThreadEventExecutor.<span class="hljs-keyword">this</span>.run();
                success = <span class="hljs-keyword">true</span>;
            &#125; 
            
            ...
    &#125;);
&#125;</code></pre>

<p>通过<code>SingleThreadEventExecutor.this.run()</code>执行传入的任务（task）</p>
<p>该run方法是<strong>NioEvnetLoop的run方法</strong></p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
    <span class="hljs-keyword">int</span> selectCnt = <span class="hljs-number">0</span>;
    <span class="hljs-comment">// 死循环，不断地从任务队列中获取各种任务来执行</span>
    <span class="hljs-keyword">for</span> (;;) &#123;	
      	<span class="hljs-comment">// 执行各种任务</span>
   		<span class="hljs-keyword">try</span> &#123;
            <span class="hljs-keyword">int</span> strategy;
            <span class="hljs-keyword">try</span> &#123;
                strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                <span class="hljs-keyword">switch</span> (strategy) &#123;
                <span class="hljs-keyword">case</span> SelectStrategy.CONTINUE:
                    <span class="hljs-keyword">continue</span>;

                <span class="hljs-keyword">case</span> SelectStrategy.BUSY_WAIT:
                    <span class="hljs-comment">// fall-through to SELECT since the busy-wait is not supported with NIO</span>

                <span class="hljs-keyword">case</span> SelectStrategy.SELECT:
                    <span class="hljs-keyword">long</span> curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                    <span class="hljs-keyword">if</span> (curDeadlineNanos == -<span class="hljs-number">1L</span>) &#123;
                        curDeadlineNanos = NONE; <span class="hljs-comment">// nothing on the calendar</span>
                    &#125;
                    nextWakeupNanos.set(curDeadlineNanos);
                    <span class="hljs-keyword">try</span> &#123;
                        <span class="hljs-keyword">if</span> (!hasTasks()) &#123;
                            strategy = select(curDeadlineNanos);
                        &#125;
                    &#125; <span class="hljs-keyword">finally</span> &#123;
                        <span class="hljs-comment">// This update is just to help block unnecessary selector wakeups</span>
                        <span class="hljs-comment">// so use of lazySet is ok (no race condition)</span>
                        nextWakeupNanos.lazySet(AWAKE);
                    &#125;
                    <span class="hljs-comment">// fall through</span>
                <span class="hljs-keyword">default</span>:
                &#125;
       		&#125;
    	&#125;
	&#125;</code></pre>

<h3 id=""><a href="#" class="headerlink" title=""></a></h3><h4 id="唤醒"><a href="#唤醒" class="headerlink" title="唤醒"></a>唤醒</h4><p>NioEvnetLoop需要IO事件、普通任务以及定时任务，任务在run方法的for循环中</p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
    <span class="hljs-keyword">int</span> selectCnt = <span class="hljs-number">0</span>;
    <span class="hljs-comment">// 死循环，不断地从任务队列中获取各种任务来执行</span>
    <span class="hljs-keyword">for</span> (;;) &#123;
      	<span class="hljs-comment">// 执行各种任务</span>
   		...
    &#125;
&#125;</code></pre>

<p>中被执行，但<strong>该循环不会空转，执行到某些代码时，会被阻塞</strong></p>
<p>run方法中有SELECT分支</p>
<pre><code class="hljs java"><span class="hljs-keyword">case</span> SelectStrategy.SELECT:
	<span class="hljs-keyword">long</span> curDeadlineNanos = nextScheduledTaskDeadlineNanos();
	<span class="hljs-keyword">if</span> (curDeadlineNanos == -<span class="hljs-number">1L</span>) &#123;
        curDeadlineNanos = NONE; <span class="hljs-comment">// nothing on the calendar</span>
    &#125;
	nextWakeupNanos.set(curDeadlineNanos);
	<span class="hljs-keyword">try</span> &#123;
    	<span class="hljs-keyword">if</span> (!hasTasks()) &#123;
            <span class="hljs-comment">// 执行select方法</span>
            strategy = select(curDeadlineNanos);
        &#125;
    &#125;
	...</code></pre>

<p>会执行NioEvnetLoop的<code>select</code>方法，<strong>该方法内部会根据情况，执行selector的有参和无参的select方法</strong></p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> <span class="hljs-title">select</span><span class="hljs-params">(<span class="hljs-keyword">long</span> deadlineNanos)</span> <span class="hljs-keyword">throws</span> IOException </span>&#123;
    <span class="hljs-comment">// 如果没有指定阻塞事件，就调用select()</span>
    <span class="hljs-keyword">if</span> (deadlineNanos == NONE) &#123;
        <span class="hljs-keyword">return</span> selector.select();
    &#125;
    <span class="hljs-comment">// 否则调用select(timeoutMillis)，指定时间内未发生事件就停止阻塞</span>
    <span class="hljs-comment">// Timeout will only be 0 if deadline is within 5 microsecs</span>
    <span class="hljs-keyword">long</span> timeoutMillis = deadlineToDelayNanos(deadlineNanos + <span class="hljs-number">995000L</span>) / <span class="hljs-number">1000000L</span>;
    <span class="hljs-keyword">return</span> timeoutMillis &lt;= <span class="hljs-number">0</span> ? selector.selectNow() : selector.select(timeoutMillis);
&#125;</code></pre>

<p>但需要注意的是，<strong><code>select</code>方法是会阻塞线程的，当没有IO事件，但有其他任务需要执行时，需要唤醒线程</strong></p>
<p>唤醒是通过execute最后的if代码块来完成的</p>
<pre><code class="hljs java"><span class="hljs-comment">// 有任务需要被执行时，唤醒阻塞的NIO线程</span>
<span class="hljs-keyword">if</span> (!addTaskWakesUp &amp;&amp; immediate) &#123;
    wakeup(inEventLoop);
&#125;</code></pre>

<p><code>NioEventLoop.wakeup</code>唤醒被selector.select方法阻塞的NIO线程</p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">wakeup</span><span class="hljs-params">(<span class="hljs-keyword">boolean</span> inEventLoop)</span> </span>&#123;
    <span class="hljs-comment">// 只有当其他线程给当前NIO线程提交任务时（如执行execute），才会被唤醒</span>
    <span class="hljs-comment">// 通过AtomicLong进行更新，保证每次只能有一个线程唤醒成功</span>
    <span class="hljs-keyword">if</span> (!inEventLoop &amp;&amp; nextWakeupNanos.getAndSet(AWAKE) != AWAKE) &#123;
        <span class="hljs-comment">// 唤醒被selector.select方法阻塞的NIO线程</span>
        selector.wakeup();
    &#125;
&#125;</code></pre>

<p>唤醒时需要进行两个判断</p>
<ul>
<li>判断提交任务的<strong>是否为NIO线程</strong><ul>
<li>若是其他线程，才能唤醒NIO线程</li>
<li>若是NIO线程自己，则不能唤醒</li>
</ul>
</li>
<li>通过<strong>AtomicLong</strong>保证有多个线程同时提交任务时，只有一个线程能够唤醒NIO线程</li>
</ul>
<h4 id="SELECT分支"><a href="#SELECT分支" class="headerlink" title="SELECT分支"></a>SELECT分支</h4><p>run方法的switch语句有多条分支，具体执行分支的代码由strategy变量控制</p>
<pre><code class="hljs java"><span class="hljs-keyword">int</span> strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
<span class="hljs-keyword">switch</span> (strategy) &#123;
	...
&#125;</code></pre>

<p>strategy的值由<code>calculateStrategy</code>方法确定</p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">calculateStrategy</span><span class="hljs-params">(IntSupplier selectSupplier, <span class="hljs-keyword">boolean</span> hasTasks)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
    <span class="hljs-comment">// selectSupplier.get() 底层是 selector.selectNow();</span>
    <span class="hljs-keyword">return</span> hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
&#125;</code></pre>

<p>该方法会<strong>根据hasTaks变量判断任务队列中是否有任务</strong></p>
<ul>
<li><p>若有任务，则<strong>通过selectSupplier获得strategy的值</strong></p>
<ul>
<li><p><strong>get方法会selectNow方法，顺便拿到IO事件</strong></p>
<pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> IntSupplier selectNowSupplier = <span class="hljs-keyword">new</span> IntSupplier() &#123;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">get</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
        <span class="hljs-keyword">return</span> NioEventLoop.<span class="hljs-keyword">this</span>.selectNow();
    &#125;
&#125;;

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">selectNow</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException </span>&#123;
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.selector.selectNow();
&#125;</code></pre>
</li>
</ul>
</li>
<li><p>若没有任务，就会进入SELECT分支</p>
</li>
</ul>
<p>也就说，<strong>当任务队列中没有任务时，才会进入SELECT分支，让NIO线程阻塞，而不是空转。若有任务，则会通过<code>get</code>方法调用<code>selector.selectNow</code>方法，顺便拿到IO事件</strong></p>
<h3 id="Java-NIO空轮询BUG"><a href="#Java-NIO空轮询BUG" class="headerlink" title="Java NIO空轮询BUG"></a>Java NIO空轮询BUG</h3><p> Java NIO空轮询BUG也就是JavaNIO在Linux系统下的epoll空轮询问题</p>
<p><strong>在NioEventLoop中，因为run方法中存在一个死循环，需要通过selector.select方法来阻塞线程。但是select方法因为BUG，可能无法阻塞线程，导致循环一直执行，使得CPU负载升高</strong></p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
    ...
    <span class="hljs-keyword">for</span>(;;)&#123;
        ...
        <span class="hljs-comment">// 可能发生空轮询，无法阻塞NIO线程</span>
        strategy = select(curDeadlineNanos);  
        ...     
    
     	<span class="hljs-keyword">if</span>(...) &#123;
			...
     	&#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (unexpectedSelectorWakeup(selectCnt) )&#123;
            <span class="hljs-comment">// 通过unexpectedSelectorWakeup方法中的rebuildSelector重建selector</span>
            <span class="hljs-comment">// 并将selectCnt重置为0</span>
            selectCnt = <span class="hljs-number">0</span>;
        &#125;
	&#125;
&#125;</code></pre>

<p><strong>Netty中通过<code>selectCnt</code>变量来检测<code>select</code>方法是否发生空轮询BUG</strong></p>
<p>若发生空轮询BUG，那么selectCnt的值会<strong>增长是十分迅速</strong>。当<code>selectCnt</code>的值大于等于<code>SELECTOR_AUTO_REBUILD_THRESHOLD</code>（默认512）时，Netty则判断其出现了空轮询BUG，进行如下处理</p>
<pre><code class="hljs java"><span class="hljs-keyword">if</span> (SELECTOR_AUTO_REBUILD_THRESHOLD &gt; <span class="hljs-number">0</span> &amp;&amp; selectCnt &gt;= SELECTOR_AUTO_REBUILD_THRESHOLD) &#123;
    <span class="hljs-comment">// The selector returned prematurely many times in a row.</span>
    <span class="hljs-comment">// Rebuild the selector to work around the problem.</span>
    logger.warn(<span class="hljs-string">"Selector.select() returned prematurely &#123;&#125; times in a row; rebuilding Selector &#123;&#125;."</span>,selectCnt, selector);
    <span class="hljs-comment">// 重建selector，将原selector的配置信息传给新selector</span>
    <span class="hljs-comment">// 再用新selector覆盖旧selector</span>
    rebuildSelector();
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
&#125;</code></pre>

<p><strong>通过<code>rebuildSelector</code>方法重建selector，将原selector的配置信息传给新selector，再用新selector覆盖旧selector。同时将selectCnt的值设置为0</strong></p>
<h3 id="ioRatio"><a href="#ioRatio" class="headerlink" title="ioRatio"></a>ioRatio</h3><p>NioEventLoop可以处理IO事件和其他任务。不同的操作所耗费的时间是不同的，<strong>想要控制NioEventLoop处理IO事件花费时间占执行所有操作的总时间的比例，需要通过ioRatio来控制</strong></p>
<p><strong>NioEventLoop.run方法</strong></p>
<pre><code class="hljs java"><span class="hljs-comment">// 处理IO事件时间比例，默认为50%</span>
<span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> ioRatio = <span class="hljs-keyword">this</span>.ioRatio;

<span class="hljs-comment">// 如果IO事件时间比例设置为100%</span>
<span class="hljs-keyword">if</span> (ioRatio == <span class="hljs-number">100</span>) &#123;
    <span class="hljs-keyword">try</span> &#123;
        <span class="hljs-comment">// 如果需要去处理IO事件</span>
        <span class="hljs-keyword">if</span> (strategy &gt; <span class="hljs-number">0</span>) &#123;
            <span class="hljs-comment">// 先处理IO事件</span>
            processSelectedKeys();
        &#125;
    &#125; <span class="hljs-keyword">finally</span> &#123;
        <span class="hljs-comment">// Ensure we always run tasks.</span>
        <span class="hljs-comment">// 剩下的时间都去处理普通任务和定时任务</span>
        ranTasks = runAllTasks();
    &#125;
&#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (strategy &gt; <span class="hljs-number">0</span>) &#123; <span class="hljs-comment">// 如果需要去处理IO事件</span>
    <span class="hljs-comment">// 记录处理IO事件前的时间</span>
    <span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> ioStartTime = System.nanoTime();
    <span class="hljs-keyword">try</span> &#123;
        <span class="hljs-comment">// 去处理IO事件</span>
        processSelectedKeys();
    &#125; <span class="hljs-keyword">finally</span> &#123;
        <span class="hljs-comment">// Ensure we always run tasks.</span>
        <span class="hljs-comment">// ioTime为处理IO事件耗费的事件</span>
        <span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> ioTime = System.nanoTime() - ioStartTime;
        <span class="hljs-comment">// 计算出处理其他任务的事件</span>
        <span class="hljs-comment">// 超过设定的时间后，将会停止任务的执行，会在下一次循环中再继续执行</span>
        ranTasks = runAllTasks(ioTime * (<span class="hljs-number">100</span> - ioRatio) / ioRatio);
    &#125;
&#125; <span class="hljs-keyword">else</span> &#123; <span class="hljs-comment">// 没有IO事件需要处理</span>
    <span class="hljs-comment">// This will run the minimum number of tasks</span>
    <span class="hljs-comment">// 直接处理普通和定时任务</span>
    ranTasks = runAllTasks(<span class="hljs-number">0</span>); 
&#125;</code></pre>

<p><strong>通过ioRatio控制各个任务执行的过程如下</strong></p>
<ul>
<li><p>判断ioRatio是否为100</p>
<ul>
<li><p>若是，判断是否需要处理IO事件（strategy&gt;0）</p>
<ul>
<li>若需要处理IO事件，则先处理IO事件</li>
</ul>
</li>
<li><p>若否（或IO事件已经处理完毕），<strong>接下来去执行所有的普通任务和定时任务，直到所有任务都被处理完</strong></p>
<pre><code class="hljs java"><span class="hljs-comment">// 没有指定执行任务的时间</span>
ranTasks = runAllTasks();</code></pre>
</li>
</ul>
</li>
<li><p>若ioRatio不为100</p>
<ul>
<li><p>先去处理IO事件，<strong>记录处理IO事件所花费的事件保存在ioTime中</strong></p>
</li>
<li><p>接下来去处理其他任务，<strong>根据ioTime与ioRatio计算执行其他任务可用的时间</strong></p>
<pre><code class="hljs java"><span class="hljs-comment">// 比如ioTime为10s，ioRatio为50</span>
<span class="hljs-comment">// 那么通过 10*(100-50)/50=10 计算出其他任务可用的时间为 10s</span>
<span class="hljs-comment">// 处理IO事件占用的事件总比例为50%</span>
ranTasks = runAllTasks(ioTime * (<span class="hljs-number">100</span> - ioRatio) / ioRatio);</code></pre>
</li>
<li><p>执行其他任务一旦超过可用时间，则会停止执行，在下一次循环中再继续执行</p>
</li>
</ul>
</li>
<li><p>若没有IO事件需要处理，则去执行<strong>最少数量</strong>的普通任务和定时任务</p>
<pre><code class="hljs java"><span class="hljs-comment">// 运行最少数量的任务</span>
ranTasks = runAllTasks(<span class="hljs-number">0</span>);</code></pre>





</li>
</ul>
<h3 id="处理事件"><a href="#处理事件" class="headerlink" title="处理事件"></a>处理事件</h3><p>IO事件是通过<code>NioEventLoop.processSelectedKeys()</code>方法处理的</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">processSelectedKeys</span><span class="hljs-params">()</span> </span>&#123;
    <span class="hljs-comment">// 如果selectedKeys是基于数组的</span>
    <span class="hljs-comment">// 一般情况下都走这个分支</span>
    <span class="hljs-keyword">if</span> (selectedKeys != <span class="hljs-keyword">null</span>) &#123;
        <span class="hljs-comment">// 处理各种IO事件</span>
        processSelectedKeysOptimized();
    &#125; <span class="hljs-keyword">else</span> &#123;
        processSelectedKeysPlain(selector.selectedKeys());
    &#125;
&#125;</code></pre>

<p>processSelectedKeysOptimized方法</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">processSelectedKeysOptimized</span><span class="hljs-params">()</span> </span>&#123;
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; selectedKeys.size; ++i) &#123;
        <span class="hljs-comment">// 拿到SelectionKeyec</span>
        <span class="hljs-keyword">final</span> SelectionKey k = selectedKeys.keys[i];
        <span class="hljs-comment">// null out entry in the array to allow to have it GC'ed once the Channel close</span>
        <span class="hljs-comment">// See https://github.com/netty/netty/issues/2363</span>
        selectedKeys.keys[i] = <span class="hljs-keyword">null</span>;

        <span class="hljs-comment">// 获取SelectionKey上的附件，即NioServerSocketChannel</span>
        <span class="hljs-keyword">final</span> Object a = k.attachment();

        <span class="hljs-keyword">if</span> (a <span class="hljs-keyword">instanceof</span> AbstractNioChannel) &#123;
            <span class="hljs-comment">// 处理事件，传入附件NioServerSocketChannel</span>
            processSelectedKey(k, (AbstractNioChannel) a);
        &#125; <span class="hljs-keyword">else</span> &#123;
            <span class="hljs-meta">@SuppressWarnings</span>(<span class="hljs-string">"unchecked"</span>)
            NioTask&lt;SelectableChannel&gt; task = (NioTask&lt;SelectableChannel&gt;) a;
            processSelectedKey(k, task);
        &#125;

        <span class="hljs-keyword">if</span> (needsToSelectAgain) &#123;
            <span class="hljs-comment">// null out entries in the array to allow to have it GC'ed once the Channel close</span>
            <span class="hljs-comment">// See https://github.com/netty/netty/issues/2363</span>
            selectedKeys.reset(i + <span class="hljs-number">1</span>);

            selectAgain();
            i = -<span class="hljs-number">1</span>;
        &#125;
    &#125;
&#125;</code></pre>

<p>该方法中通过fori的方法，遍历基于数组的SelectedKey，通过</p>
<pre><code class="hljs java"><span class="hljs-keyword">final</span> SelectionKey k = selectedKeys.keys[i];</code></pre>

<p>获取到SelectionKey，<strong>然后获取其再Register时添加的附件<code>NioServerSocketChannel</code></strong></p>
<pre><code class="hljs java"><span class="hljs-comment">// 获取SelectionKey上的附件，即NioServerSocketChannel</span>
<span class="hljs-keyword">final</span> Object a = k.attachment();</code></pre>

<p>如果附件继承自AbstractNioChannel，则会调用</p>
<pre><code class="hljs java"><span class="hljs-comment">// 处理事件，传入附件NioServerSocketChannel</span>
processSelectedKey(k, (AbstractNioChannel) a);</code></pre>

<p>去处理各个事件</p>
<p><strong>真正处理各种事件的方法<code>processSelectedKey</code></strong></p>
<p>获取SelectionKey的事件，然后进行相应处理</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">processSelectedKey</span><span class="hljs-params">(SelectionKey k, AbstractNioChannel ch)</span> </span>&#123;
    <span class="hljs-keyword">final</span> AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    <span class="hljs-keyword">if</span> (!k.isValid()) &#123;
        <span class="hljs-keyword">final</span> EventLoop eventLoop;
        <span class="hljs-keyword">try</span> &#123;
            eventLoop = ch.eventLoop();
        &#125; <span class="hljs-keyword">catch</span> (Throwable ignored) &#123;
            <span class="hljs-comment">// If the channel implementation throws an exception because there is no event loop, we ignore this</span>
            <span class="hljs-comment">// because we are only trying to determine if ch is registered to this event loop and thus has authority</span>
            <span class="hljs-comment">// to close ch.</span>
            <span class="hljs-keyword">return</span>;
        &#125;
        <span class="hljs-comment">// Only close ch if ch is still registered to this EventLoop. ch could have deregistered from the event loop</span>
        <span class="hljs-comment">// and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is</span>
        <span class="hljs-comment">// still healthy and should not be closed.</span>
        <span class="hljs-comment">// See https://github.com/netty/netty/issues/5125</span>
        <span class="hljs-keyword">if</span> (eventLoop == <span class="hljs-keyword">this</span>) &#123;
            <span class="hljs-comment">// close the channel if the key is not valid anymore</span>
            unsafe.close(unsafe.voidPromise());
        &#125;
        <span class="hljs-keyword">return</span>;
    &#125;

    <span class="hljs-keyword">try</span> &#123;
        <span class="hljs-keyword">int</span> readyOps = k.readyOps();
        <span class="hljs-comment">// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise</span>
        <span class="hljs-comment">// the NIO JDK channel implementation may throw a NotYetConnectedException.</span>
        <span class="hljs-keyword">if</span> ((readyOps &amp; SelectionKey.OP_CONNECT) != <span class="hljs-number">0</span>) &#123;
            <span class="hljs-comment">// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking</span>
            <span class="hljs-comment">// See https://github.com/netty/netty/issues/924</span>
            <span class="hljs-keyword">int</span> ops = k.interestOps();
            ops &amp;= ~SelectionKey.OP_CONNECT;
            k.interestOps(ops);

            unsafe.finishConnect();
        &#125;

        <span class="hljs-comment">// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.</span>
        <span class="hljs-keyword">if</span> ((readyOps &amp; SelectionKey.OP_WRITE) != <span class="hljs-number">0</span>) &#123;
            <span class="hljs-comment">// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write</span>
            ch.unsafe().forceFlush();
        &#125;

        <span class="hljs-comment">// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead</span>
        <span class="hljs-comment">// to a spin loop</span>
        <span class="hljs-keyword">if</span> ((readyOps &amp; (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != <span class="hljs-number">0</span> || readyOps == <span class="hljs-number">0</span>) &#123;
            unsafe.read();
        &#125;
    &#125; <span class="hljs-keyword">catch</span> (CancelledKeyException ignored) &#123;
        unsafe.close(unsafe.voidPromise());
    &#125;
&#125;</code></pre>





<h2 id="3、Accept剖析"><a href="#3、Accept剖析" class="headerlink" title="3、Accept剖析"></a>3、Accept剖析</h2><h3 id="NIO中处理Accept事件流程"><a href="#NIO中处理Accept事件流程" class="headerlink" title="NIO中处理Accept事件流程"></a>NIO中处理Accept事件流程</h3><p>NIO中处理Accept事件主要有以下六步</p>
<ul>
<li>selector.select()阻塞线程，直到事件发生</li>
<li>遍历selectionKeys</li>
<li>获取一个key，判断事件类型是否为Accept</li>
</ul>
<hr>
<ul>
<li>创建SocketChannel，设置为非阻塞</li>
<li>将SocketChannel注册到selector中</li>
<li>关注selectionKeys的read事件</li>
</ul>
<p>代码如下</p>
<pre><code class="hljs java"><span class="hljs-comment">// 阻塞直到事件发生</span>
selector.select();

Iterator&lt;SelectionKey&gt; iter = selector.selectionKeys().iterator();
<span class="hljs-keyword">while</span> (iter.hasNext()) &#123;    
    <span class="hljs-comment">// 拿到一个事件</span>
    SelectionKey key = iter.next();
    
    <span class="hljs-comment">// 如果是 accept 事件</span>
    <span class="hljs-keyword">if</span> (key.isAcceptable()) &#123;
        
        <span class="hljs-comment">// 执行accept，获得SocketChannel</span>
        SocketChannel channel = serverSocketChannel.accept();
        channel.configureBlocking(<span class="hljs-keyword">false</span>);
        
        <span class="hljs-comment">// 将SocketChannel注册到selector中，并关注read事件</span>
        channel.register(selector, SelectionKey.OP_READ);
    &#125;
    <span class="hljs-comment">// ...</span>
&#125;</code></pre>

<p><strong>其中前三步，在NioEventLoop剖析中已经分析过了，所以接下来主要分析后三步</strong></p>
<h3 id="SocketChannel的创建与注册"><a href="#SocketChannel的创建与注册" class="headerlink" title="SocketChannel的创建与注册"></a>SocketChannel的创建与注册</h3><p>发生Accept事件后，会执行<code>NioEventLoop.run</code>方法的如下if分支</p>
<pre><code class="hljs java"><span class="hljs-keyword">if</span> ((readyOps &amp; (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != <span class="hljs-number">0</span> || readyOps == <span class="hljs-number">0</span>) &#123;
	unsafe.read();
&#125;</code></pre>

<p><code>NioMessageUnsafe.read</code>方法</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">read</span><span class="hljs-params">()</span> </span>&#123;

    ...
    
    <span class="hljs-keyword">try</span> &#123;
        <span class="hljs-keyword">try</span> &#123;
            <span class="hljs-keyword">do</span> &#123;
				<span class="hljs-comment">// doReadMessages中执行了accept获得了SocketChannel</span>
                <span class="hljs-comment">// 并创建NioSocketChannel作为消息放入readBuf</span>
                <span class="hljs-comment">// readBuf是一个ArrayList用来缓存消息</span>
                <span class="hljs-comment">// private final List&lt;Object&gt; readBuf = new ArrayList&lt;Object&gt;();</span>
                <span class="hljs-keyword">int</span> localRead = doReadMessages(readBuf);
                
                ...
                
				<span class="hljs-comment">// localRead值为1，就一条消息，即接收一个客户端连接</span>
                allocHandle.incMessagesRead(localRead);
            &#125; <span class="hljs-keyword">while</span> (allocHandle.continueReading());
        &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;
            exception = t;
        &#125;

        <span class="hljs-keyword">int</span> size = readBuf.size();
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; size; i ++) &#123;
            readPending = <span class="hljs-keyword">false</span>;
            <span class="hljs-comment">// 触发read事件，让pipeline上的handler处理</span>
            <span class="hljs-comment">// ServerBootstrapAcceptor.channelRead</span>
            pipeline.fireChannelRead(readBuf.get(i));
        &#125;
        
        ...
        
    &#125; <span class="hljs-keyword">finally</span> &#123;
        <span class="hljs-keyword">if</span> (!readPending &amp;&amp; !config.isAutoRead()) &#123;
            removeReadOp();
        &#125;
    &#125;
&#125;</code></pre>

<p><code>NioSocketChannel.doReadMessages</code>方法</p>
<p>该方法中处理accpet事件，<strong>获得SocketChannel</strong>，同时<strong>创建了NioSocketChannel</strong>，作为消息放在了readBuf中</p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">int</span> <span class="hljs-title">doReadMessages</span><span class="hljs-params">(List&lt;Object&gt; buf)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
    <span class="hljs-comment">// 处理accpet事件，获得SocketChannel</span>
    SocketChannel ch = SocketUtils.accept(javaChannel());

    <span class="hljs-keyword">try</span> &#123;
        <span class="hljs-keyword">if</span> (ch != <span class="hljs-keyword">null</span>) &#123;
            <span class="hljs-comment">// 创建了NioSocketChannel，作为消息放在了readBuf中</span>
            buf.add(<span class="hljs-keyword">new</span> NioSocketChannel(<span class="hljs-keyword">this</span>, ch));
            <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
        &#125;
    &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;
       ...
    &#125;

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
&#125;</code></pre>

<p> <code>ServerBootstrapAcceptor.channelRead</code></p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelRead</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg)</span> </span>&#123;
    <span class="hljs-comment">// 这时的msg是NioSocketChannel</span>
    <span class="hljs-keyword">final</span> Channel child = (Channel) msg;

    <span class="hljs-comment">// NioSocketChannel添加childHandler，即初始化器</span>
    child.pipeline().addLast(childHandler);

    <span class="hljs-comment">// 设置选项</span>
    setChannelOptions(child, childOptions, logger);

    <span class="hljs-keyword">for</span> (Entry&lt;AttributeKey&lt;?&gt;, Object&gt; e: childAttrs) &#123;
        child.attr((AttributeKey&lt;Object&gt;) e.getKey()).set(e.getValue());
    &#125;

    <span class="hljs-keyword">try</span> &#123;
        <span class="hljs-comment">// 注册 NioSocketChannel到nio worker线程，接下来的处理也移交至nio worker线程</span>
        childGroup.register(child).addListener(<span class="hljs-keyword">new</span> ChannelFutureListener() &#123;
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">operationComplete</span><span class="hljs-params">(ChannelFuture future)</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
                <span class="hljs-keyword">if</span> (!future.isSuccess()) &#123;
                    forceClose(child, future.cause());
                &#125;
            &#125;
        &#125;);
    &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;
        forceClose(child, t);
    &#125;
&#125;</code></pre>



<p>通过<code>AbstractUnsafe.register</code>  方法，将SocketChannel注册到了Selector中，<strong>过程与启动流程中的Register过程类似</strong></p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title">register</span><span class="hljs-params">(EventLoop eventLoop, <span class="hljs-keyword">final</span> ChannelPromise promise)</span> </span>&#123;
    
    ...

    AbstractChannel.<span class="hljs-keyword">this</span>.eventLoop = eventLoop;

    <span class="hljs-keyword">if</span> (eventLoop.inEventLoop()) &#123;
        register0(promise);
    &#125; <span class="hljs-keyword">else</span> &#123;
        <span class="hljs-keyword">try</span> &#123;
            <span class="hljs-comment">// 这行代码完成的是nio boss -&gt; nio worker线程的切换</span>
            eventLoop.execute(<span class="hljs-keyword">new</span> Runnable() &#123;
                <span class="hljs-meta">@Override</span>
                <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>&#123;
                    <span class="hljs-comment">// 真正的注册操作</span>
                    register0(promise);
                &#125;
            &#125;);
        &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;
            ...
        &#125;
    &#125;
&#125;</code></pre>

<p><code>AbstractChannel.AbstractUnsafe.register0</code></p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">register0</span><span class="hljs-params">(ChannelPromise promise)</span> </span>&#123;
    <span class="hljs-keyword">try</span> &#123;
        
        ...
            
        <span class="hljs-comment">// 该方法将SocketChannel注册到Selector中</span>
        doRegister();
        
        <span class="hljs-comment">// 执行初始化器，执行前 pipeline 中只有 head -&gt; 初始化器 -&gt; tail</span>
        pipeline.invokeHandlerAddedIfNeeded();
        <span class="hljs-comment">// 执行后就是 head -&gt; logging handler -&gt; my handler -&gt; tail</span>

        safeSetSuccess(promise);
        pipeline.fireChannelRegistered();
        
        <span class="hljs-keyword">if</span> (isActive()) &#123;
            <span class="hljs-keyword">if</span> (firstRegistration) &#123;
                <span class="hljs-comment">// 触发pipeline上active事件</span>
                pipeline.fireChannelActive();
            &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (config().isAutoRead()) &#123;
                beginRead();
            &#125;
        &#125;
    &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;
        closeForcibly();
        closeFuture.setClosed();
        safeSetFailure(promise, t);
    &#125;
&#125;</code></pre>

<p><code>AbstractNioChannel.doRegister</code>将SocketChannel注册到Selector中</p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doRegister</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
    <span class="hljs-keyword">boolean</span> selected = <span class="hljs-keyword">false</span>;
    <span class="hljs-keyword">for</span> (;;) &#123;
        <span class="hljs-keyword">try</span> &#123;
            <span class="hljs-comment">// 将Selector注册到Selector中</span>
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), <span class="hljs-number">0</span>, <span class="hljs-keyword">this</span>);
            <span class="hljs-keyword">return</span>;
        &#125; <span class="hljs-keyword">catch</span> (CancelledKeyException e) &#123;
            ...
        &#125;
    &#125;
&#125;</code></pre>

<p> <code>HeadContext.channelActive</code></p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">channelActive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> </span>&#123;
    ctx.fireChannelActive();
	<span class="hljs-comment">// 触发read(NioSocketChannel这里read只是为了触发channel的事件注册，还未涉及数据读取)</span>
    readIfIsAutoRead();
&#125;</code></pre>

<p><code>AbstractNioChannel.doBeginRead</code>，通过该方法，SocketChannel关注了read事件</p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doBeginRead</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;
    <span class="hljs-comment">// Channel.read() or ChannelHandlerContext.read() was called</span>
    <span class="hljs-keyword">final</span> SelectionKey selectionKey = <span class="hljs-keyword">this</span>.selectionKey;
    <span class="hljs-keyword">if</span> (!selectionKey.isValid()) &#123;
        <span class="hljs-keyword">return</span>;
    &#125;

    readPending = <span class="hljs-keyword">true</span>;
	<span class="hljs-comment">// 这时候 interestOps是0</span>
    <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> interestOps = selectionKey.interestOps();
    <span class="hljs-keyword">if</span> ((interestOps &amp; readInterestOp) == <span class="hljs-number">0</span>) &#123;
        <span class="hljs-comment">// 关注read事件</span>
        selectionKey.interestOps(interestOps | readInterestOp);
    &#125;
&#125;</code></pre>



<h2 id="4、Read剖析"><a href="#4、Read剖析" class="headerlink" title="4、Read剖析"></a>4、Read剖析</h2><p>read事件的处理也是在</p>
<pre><code class="hljs java"><span class="hljs-keyword">if</span> ((readyOps &amp; (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != <span class="hljs-number">0</span> || readyOps == <span class="hljs-number">0</span>) &#123;
	unsafe.read();
&#125;</code></pre>

<p>分支中，通过<code>unsafe.read()</code>方法处理的，<strong>不过此处调用的方法在AbstractNioByteChannel.NioByteUnsafe类中</strong></p>
<pre><code class="hljs java"><span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title">read</span><span class="hljs-params">()</span> </span>&#123;
    <span class="hljs-comment">// 获得Channel的配置</span>
    <span class="hljs-keyword">final</span> ChannelConfig config = config();
    <span class="hljs-keyword">if</span> (shouldBreakReadReady(config)) &#123;
        clearReadPending();
        <span class="hljs-keyword">return</span>;
    &#125;
    <span class="hljs-keyword">final</span> ChannelPipeline pipeline = pipeline();
	<span class="hljs-comment">// 根据配置创建ByteBufAllocator（池化非池化、直接非直接内存）</span>
	<span class="hljs-keyword">final</span> ByteBufAllocator allocator = config.getAllocator();
    <span class="hljs-comment">// 用来分配 byteBuf，确定单次读取大小</span>
    <span class="hljs-keyword">final</span> RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
    allocHandle.reset(config);

    ByteBuf byteBuf = <span class="hljs-keyword">null</span>;
    <span class="hljs-keyword">boolean</span> close = <span class="hljs-keyword">false</span>;
    <span class="hljs-keyword">try</span> &#123;
        <span class="hljs-keyword">do</span> &#123;
            <span class="hljs-comment">// 创建ByteBuf</span>
            byteBuf = allocHandle.allocate(allocator);
            <span class="hljs-comment">// 读取内容，放入ByteBUf中</span>
            allocHandle.lastBytesRead(doReadBytes(byteBuf));
            <span class="hljs-keyword">if</span> (allocHandle.lastBytesRead() &lt;= <span class="hljs-number">0</span>) &#123;
                byteBuf.release();
                byteBuf = <span class="hljs-keyword">null</span>;
                close = allocHandle.lastBytesRead() &lt; <span class="hljs-number">0</span>;
                <span class="hljs-keyword">if</span> (close) &#123;
                    readPending = <span class="hljs-keyword">false</span>;
                &#125;
                <span class="hljs-keyword">break</span>;
            &#125;

            allocHandle.incMessagesRead(<span class="hljs-number">1</span>);
            readPending = <span class="hljs-keyword">false</span>;
            <span class="hljs-comment">// 触发read 事件，让pipeline上的handler处理</span>
            <span class="hljs-comment">// 这时是处理NioSocketChannel上的handler</span>
            pipeline.fireChannelRead(byteBuf);
            byteBuf = <span class="hljs-keyword">null</span>;
        &#125; 
        <span class="hljs-comment">// 是否要继续循环</span>
        <span class="hljs-keyword">while</span> (allocHandle.continueReading());

        allocHandle.readComplete();
        <span class="hljs-comment">// 触发 read complete事件</span>
        pipeline.fireChannelReadComplete();

        <span class="hljs-keyword">if</span> (close) &#123;
            closeOnRead(pipeline);
        &#125;
    &#125; <span class="hljs-keyword">catch</span> (Throwable t) &#123;
        handleReadException(pipeline, byteBuf, t, close, allocHandle);
    &#125; <span class="hljs-keyword">finally</span> &#123;
         <span class="hljs-comment">// Check if there is a readPending which was not processed yet.</span>
         <span class="hljs-comment">// This could be for two reasons:</span>
         <span class="hljs-comment">// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method</span>
         <span class="hljs-comment">// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method</span>
         <span class="hljs-comment">//</span>
         <span class="hljs-comment">// See https://github.com/netty/netty/issues/2254</span>
        <span class="hljs-keyword">if</span> (!readPending &amp;&amp; !config.isAutoRead()) &#123;
            removeReadOp();
        &#125;
    &#125;
&#125;</code></pre>

<p><code>DefaultMaxMessagesRecvByteBufAllocator.MaxMessageHandle.continueReading(io.netty.util.UncheckedBooleanSupplier)</code></p>
<pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">continueReading</span><span class="hljs-params">(UncheckedBooleanSupplier maybeMoreDataSupplier)</span> </span>&#123;
    <span class="hljs-keyword">return</span> 
           <span class="hljs-comment">// 一般为true</span>
           config.isAutoRead() &amp;&amp;
           <span class="hljs-comment">// respectMaybeMoreData默认为true</span>
           <span class="hljs-comment">// maybeMoreDataSupplier的逻辑是如果预期读取字节与实际读取字节相等，返回true</span>
           (!respectMaybeMoreData || maybeMoreDataSupplier.get()) &amp;&amp;
           <span class="hljs-comment">// 小于最大次数，maxMessagePerRead默认16</span>
           totalMessages &lt; maxMessagePerRead &amp;&amp;
           <span class="hljs-comment">// 实际读到了数据</span>
           totalBytesRead &gt; <span class="hljs-number">0</span>;
&#125;</code></pre>


            </article>
            <hr>
            <div>
              <div class="post-metas mb-3">
                
                  <div class="post-meta mr-3">
                    <i class="iconfont icon-category"></i>
                    
                      <a class="hover-with-bg" href="/categories/Netty/">Netty</a>
                    
                  </div>
                
                
              </div>
              
                <p class="note note-warning">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.zh" target="_blank" rel="nofollow noopener noopener">CC BY-SA 4.0 协议</a> ，转载请注明出处！</p>
              
              
                <div class="post-prevnext row">
                  <div class="post-prev col-6">
                    
                    
                  </div>
                  <div class="post-next col-6">
                    
                    
                      <a href="/2021/04/18/Netty%E5%AD%A6%E4%B9%A0%E4%B9%8BNIO%E5%9F%BA%E7%A1%80/">
                        <span class="hidden-mobile">Netty学习之NIO基础</span>
                        <span class="visible-mobile">Next</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </div>
                </div>
              
            </div>

            
              <!-- Comments -->
              <div class="comments" id="comments">
                
                
  <div class="disqus" style="width:100%">
    <div id="disqus_thread"></div>
    <script type="text/javascript">
      function loadDisqus() {
        var disqus_config = function () {
          this.page.url = 'http://nyimac.gitee.io/2021/04/25/Netty基础/';
          this.page.identifier = '/2021/04/25/Netty基础/';
        };
        (function () {
          var d = document,
            s = d.createElement('script');
          s.src = '//' + '' + '.disqus.com/embed.js';
          s.setAttribute('data-timestamp', new Date());
          (d.head || d.body).appendChild(s);
        })();
      }
      createObserver(loadDisqus, 'disqus_thread');
    </script>
    <noscript>Please enable JavaScript to view the
      <a href="https://disqus.com/?ref_noscript" target="_blank" rel="nofollow noopener noopener">comments powered by Disqus.</a>
    </noscript>
  </div>


              </div>
            
          </div>
        </div>
      </div>
    </div>
    
      <div class="d-none d-lg-block col-lg-2 toc-container" id="toc-ctn">
        <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;TOC</p>
  <div id="tocbot"></div>
</div>

      </div>
    
  </div>
</div>

<!-- Custom -->


    
  </main>

  
    <a id="scroll-top-button" href="#" role="button">
      <i class="iconfont icon-arrowup" aria-hidden="true"></i>
    </a>
  

  
    <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">Search</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v"
                 for="local-search-input">keyword</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>
  

  

  

  <footer class="mt-5">
  <div class="text-center py-3">
    <div>
      <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a>
      <i class="iconfont icon-love"></i>
      <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener">
        <span>Fluid</span></a>
    </div>
    
  <div class="statistics">
    
    

    
      
        <!-- 不蒜子统计PV -->
        <span id="busuanzi_container_site_pv" style="display: none">
            总访问量 
            <span id="busuanzi_value_site_pv"></span>
             次
          </span>
      
      
        <!-- 不蒜子统计UV -->
        <span id="busuanzi_container_site_uv" style="display: none">
            总访客数 
            <span id="busuanzi_value_site_uv"></span>
             人
          </span>
      
    
  </div>


    

    
  </div>
</footer>

<!-- SCRIPTS -->
<script  src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js" ></script>
<script  src="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/js/bootstrap.min.js" ></script>
<script  src="/js/debouncer.js" ></script>
<script  src="/js/main.js" ></script>

<!-- Plugins -->


  
    <script  src="/js/lazyload.js" ></script>
  



  <script defer src="https://cdn.staticfile.org/clipboard.js/2.0.6/clipboard.min.js" ></script>
  <script  src="/js/clipboard-use.js" ></script>



  <script defer src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" ></script>





  <script  src="https://cdn.staticfile.org/tocbot/4.11.1/tocbot.min.js" ></script>
  <script>
    $(document).ready(function () {
      var boardCtn = $('#board-ctn');
      var boardTop = boardCtn.offset().top;

      tocbot.init({
        tocSelector: '#tocbot',
        contentSelector: 'article.markdown-body',
        headingSelector: 'h1,h2,h3,h4,h5,h6',
        linkClass: 'tocbot-link',
        activeLinkClass: 'tocbot-active-link',
        listClass: 'tocbot-list',
        isCollapsedClass: 'tocbot-is-collapsed',
        collapsibleClass: 'tocbot-is-collapsible',
        collapseDepth: 0,
        scrollSmooth: true,
        headingsOffset: -boardTop
      });
      if ($('.toc-list-item').length > 0) {
        $('#toc').css('visibility', 'visible');
      }
    });
  </script>



  <script  src="https://cdn.staticfile.org/typed.js/2.0.11/typed.min.js" ></script>
  <script>
    var typed = new Typed('#subtitle', {
      strings: [
        '  ',
        "Netty&nbsp;",
      ],
      cursorChar: "_",
      typeSpeed: 70,
      loop: false,
    });
    typed.stop();
    $(document).ready(function () {
      $(".typed-cursor").addClass("h2");
      typed.start();
    });
  </script>



  <script  src="https://cdn.staticfile.org/anchor-js/4.2.2/anchor.min.js" ></script>
  <script>
    anchors.options = {
      placement: "right",
      visible: "hover",
      
    };
    var el = "h1,h2,h3,h4,h5,h6".split(",");
    var res = [];
    for (item of el) {
      res.push(".markdown-body > " + item)
    }
    anchors.add(res.join(", "))
  </script>



  <script  src="/js/local-search.js" ></script>
  <script>
    var path = "/local-search.xml";
    var inputArea = document.querySelector("#local-search-input");
    inputArea.onclick = function () {
      searchFunc(path, 'local-search-input', 'local-search-result');
      this.onclick = null
    }
  </script>



  <script  src="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.js" ></script>
  <link  rel="stylesheet" href="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.css" />

  <script>
    $('#post img:not(.no-zoom img, img[no-zoom]), img[zoom]').each(
      function () {
        var element = document.createElement('a');
        $(element).attr('data-fancybox', 'images');
        $(element).attr('href', $(this).attr('src'));
        $(this).wrap(element);
      }
    );
  </script>

















  
    <!-- Baidu Analytics -->
    <script defer>
      var _hmt = _hmt || [];
      (function () {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?ba41ec605b9b7320e120275462e4035b";
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(hm, s);
      })();
    </script>
  

  

  

  

  

  





</body>
</html>
